Tasks
Each of the following tasks is worth 20 points, for a total of 100 points for this project. Remember to build each model as specified, evaluate it using the strategy outlined above, and plot the training and test errors by training set size (%).
1. Numeric variables
Use Linear Regression to predict Gross based on available numeric variables. You can choose to include all or a subset of them.
# TODO: Build & evaluate model 1 (numeric variables only)
numeric_cols = colnames(df[, sapply(df, is.numeric)])
print(numeric_cols)
[1] "Runtime" "imdbRating" "imdbVotes" "Budget"
[5] "Gross" "Date" "released_year"
print(summary(df[, numeric_cols]))
Runtime imdbRating imdbVotes Budget
Min. : 1.0 Min. :1.600 Min. : 5 Min. : 1100
1st Qu.: 93.0 1st Qu.:5.700 1st Qu.: 8962 1st Qu.: 5250000
Median :102.0 Median :6.400 Median : 36268 Median : 20000000
Mean :104.9 Mean :6.294 Mean : 84748 Mean : 34625509
3rd Qu.:115.0 3rd Qu.:7.100 3rd Qu.: 101076 3rd Qu.: 45000000
Max. :219.0 Max. :9.000 Max. :1670736 Max. :425000000
NA's :21 NA's :22 NA's :22
Gross Date released_year
Min. :0.000e+00 Min. :1999 Min. :2000
1st Qu.:2.924e+06 1st Qu.:2004 1st Qu.:2004
Median :3.036e+07 Median :2008 Median :2008
Mean :9.460e+07 Mean :2008 Mean :2008
3rd Qu.:1.021e+08 3rd Qu.:2012 3rd Qu.:2012
Max. :2.784e+09 Max. :2016 Max. :2016
# Number of times to repeat training for stability
num_repeat = 10
model1_cols = c('released_year', 'Runtime', 'imdbRating', 'imdbVotes', 'Budget')
model1_exp = paste('Gross~', paste(model1_cols, collapse='+'))
eval_model1 = eval_model(train_df, test_df, model1_exp, train_sizes, num_repeat=10)
df_eval = eval_model1$df_eval
Q: List the numeric variables you used.
A: The numeric variables that we used were Runtime, imdbRating, imdbVotes, Budget, Gross, Date, released_year. (Recall that we stripped out the tomato-like columns in our preprocessing steps above.)
Q: What is the best mean test RMSE value you observed, and at what training set size?
A:
# Plotting Training and Test RMSE
ggplot(data=df_eval, aes(x=train_size, y=rmses)) +
geom_line(aes(color=rmse_type, group=rmse_type)) +
ggtitle('Model 1: Training and Test RMSE')

# Best test RMSE
best_testrmse_mod1 = eval_model1$best_test_rmse
best_train_size_mod1 = eval_model1$best_train_size
The best mean test RMSE value I observed was 8.745068210^{7} and I observed that at the training set size 80 percent. Notice from the graph above as well that we see that Test RMSE generally decreases with higher training set. This makes sense because with very low training set size, the model will overfit the more limited data.
2. Feature transformations
Try to improve the prediction quality from Task 1 as much as possible by adding feature transformations of the numeric variables. Explore both numeric transformations such as power transforms and non-numeric transformations of the numeric variables like binning (e.g. is_budget_greater_than_3M).
# Helper function to plot relative proportions
plot_relative_proportions = function(df, cols){
# Keyword Args:
# df: DataFrame with columns you want to plot relative proportions of
# cols: The columns you want to plot relative proportions of
# Returns:
# plot_proportions: ggplot of proportions
# top_cols: Vector of Column Counts
df.subset = df[c('Title', cols)]
df.long = melt(df.subset, id.vars=c('Title'))
# Removing rows where value is 0 because when value is 0, that means movie is not that genre.
df.long = df.long[apply(df.long['value'], 1, function(z) !any(z==0)), ]
# Finally plot relative proportions
# https://sebastiansauer.github.io/percentage_plot_ggplot2_V2/
plot_proportions = ggplot(df.long, aes(x=variable)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
ylab('Relative Proportion')
top_cols = sort(colSums(df[cols]), decreasing=TRUE)
return(list('plot_proportions'=plot_proportions,
'top_cols'=top_cols))
}
# From Lesson 8: Preprocessing Data (22. Skewness and Power Transformations)
# Also from Lessong 10: Logistic Regression (19. Increasing Data Dimensionality)
# The Transforms we are going to use are
# 1. x^2
# 2. log(x) (in theory we could choose difference values of lambda to choose different types of power transformations. In this case for simplicity, we will stick with log transformations (e.g. lambda = 0))
# Helper function to create power transforms
create_power_transforms = function(df, column){
# Keyword Args:
# df: DataFrame with data that you want to add power transformations to
# column: Column within df that you want to do power transformations to
# Returns:
# df: DataFrame with new columns as transformations
# Power Transforms
# For now, let's only consider power transformations where lambda = 2 or -1. In theory
# we could try infinite values of lambda with some kind of cross validation approach to tune this
# but for simplicity let's just use inverse and quadratic.
df[[paste0(column, '2')]] = (df[[column]] ^ 2 - 1) / 2 # lambda = 2
df[[paste0(column, 'neg1')]] = -1 * (df[[column]] ^ (-1) - 1) / (-1) # lambda = -1
df[[paste0('log', column)]] = log(df[[column]])
return(df)
}
create_binning_transforms = function(x, threshold){
# Keyword Args:
# x: Vector of data you want to transfor
# threshold: Threshold over which transformed vector equals 1, else 0
# Returns:
# binned: New binned transformed vector
# Power Transforms
binned = as.numeric(x >= threshold)
return(binned)
}
# TODO: Build & evaluate model 2 (transformed numeric variables only)
# Creating new variables
df$is_budget_greater_than_60m = as.numeric(df$Budget >= 60000000)
df$imdbRating_greater_than_75 = as.numeric(df$imdbRating >= 7.5)
df = create_power_transforms(df, 'Budget')
df = create_power_transforms(df, 'Runtime')
df = create_power_transforms(df, 'imdbRating')
df = create_power_transforms(df, 'imdbVotes')
df = create_power_transforms(df, 'released_year')
# Adding bins by years
df$early_2000s = as.numeric(df$released_year <= 2004)
df$mid_2000s = as.numeric(df$released_year >= 2005 & df$released_year <= 2009)
df$post_2010 = as.numeric(df$released_year >= 2010)
# Adding bins by Runtime
df$RuntimeUnder75 = as.numeric(df$Runtime <= 75)
df$RuntimeBetween75_125 = as.numeric(df$Runtime > 75 & df$Runtime <= 125)
df$RuntimeGreater125 = as.numeric(df$Runtime > 125)
# "Recreate" the training and test df with the same indices to include the new columns
train_df <- df[train_idx, ]
test_df <- df[-train_idx, ]
model2_cols = c(model1_cols, c('imdbRating_greater_than_75',
'imdbRating2',
'is_budget_greater_than_60m',
'RuntimeUnder75',
'RuntimeBetween75_125',
'RuntimeGreater125',
'Runtime2',
'early_2000s',
'mid_2000s',
'post_2010'
))
model2_exp = paste('Gross~', paste(model2_cols, collapse='+'))
eval_model2 = eval_model(train_df, test_df, model2_exp, train_sizes, num_repeat=10)
df_eval_mod2 = eval_model2$df_eval
Q: Explain which transformations you used and why you chose them.
A: In addition to the features from Part 1, I created the following new features
- Binned imdbRating: Created an indicator variable for if the imdbRating was greater than 7.5
- Power Transformation for imdbRating: lambda=2
- Binned Budget: Created an indicator variable for if budget greater than 60 million
- Binned Runtime (under 75, between 75 and 125, and over 125)
- Power Transformation for Runtime: lambda=2
- Binned Released Year (between 2000 and 2004; 2005 and 2009; 2010 and after)
First, I created a binned variable for imdbRating at 7.5. (Note, later on in part 5 as we explore more interesting relationships, I interact this binned variable with imdbRating to capture a different relationship after 7.5 – more on this below).
I chose to look at imdbRating because I noticed an interesting relationship with imdbRating and Gross. If you look at the plot below, we plot the average Gross given imdbRating, and notice that the relationship seems to be linear until about 7.5. Then the average gross increases a lot. This is an interesting relationship because maybe movies below 7.5 rating are mostly not that great and so the relationship is linear. But if a movie is really really good, when the relationship between Gross and rating changes, that is a really really good movie grosses much much more. Thus, I chose to create a binned variable at imdbRating = 7.5. This will allow for the model to potentially capture a difference in the effect of rating on Gross for movies with ratings greater than 7.5. However, from the graph, notice that the slope of the relationship is not constant before and after 7.5. Thus, in part 5, we will also create an interaction variable to try to capture a different relationship after imdbRating = 7.5 with imdbRating2 where imdbRating2 is the squared term of imdbRating (since the relationship after imdbRating=7.5 looks exponential. Furthermore, looking at the GGPairs graph, it looks like imdbRating2 has a higher linear correlation with Gross than imdbRating. The other power transformations did not seem to increase linear correlation.)
ggplot(df, aes(imdbRating, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
ggtitle('Relationship between Gross and imdbRating')

ggpairs(df[c('imdbRating', 'imdbRating2', 'imdbRatingneg1', 'logimdbRating', 'Gross')])

Next, I looked at the relationship between Gross and imdbVotes. From the graphs below, it doesn’t look like there are meaningful bins that would add value. Furthermore, looking at the GGPairs plot, none of the power transformations add higher linear correlation with Gross, so it does not make sense to add additional transformations with imdbVotes into our model.
ggplot(df, aes(imdbVotes, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
ggtitle('Relationship between Gross and imdbVotes')

ggpairs(df[c('imdbVotes', 'imdbVotes2', 'imdbVotesneg1', 'logimdbVotes', 'Gross')])

Next, I look at the relationship between Budget and Gross. This is because if we remember back to Project 1, higher budget films tended to gross much higher. Looking at the graphs below, it seems like $60M would be an interesting bin. However, we also plot the GGPairs plot of Budget2 (power transformation with lambda = 2), Budgetneg1 (power transformation with lambda = -1), logBudget (power transformation with lambda = 0) and Budget against Gross and you can see at least with the linear correlation, none of the power transformations offer better linear correlation with Gross than Budget. Thus, we choose not to add any of the power transformations of Budget into this model.
# Distribution of Gross by Budget
# Now we need to show distribution of runtime by Budget. Do a boxplot grouped by budget
df$budget_rounded = round_any(df$Budget, 10000000, f = floor)
df$budget_rounded[df$Budget >= 60000000] = 60000000
df$budget_rounded = as.character(df$budget_rounded)
df$budget_rounded[df$budget_rounded == '600000000'] = 'Over 60M'
ggplot(df, aes(as.factor(budget_rounded), Gross)) +
geom_boxplot() +
coord_flip() +
scale_x_discrete("Budget Rounded") +
ggtitle('Distribution of Gross by Budget')

ggplot(df, aes(Budget, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point")

ggpairs(df[c('Budget', 'Budget2', 'Budgetneg1', 'logBudget', 'Gross')])

Next, we look at the relationship between Runtime and Gross. There are a few things that are interesting here. Notice first that if we look at the relationship between Gross and Runtime there seem to be a few interesting “cut points”. For Runtime between 0 and 75 (up until the orange dotted line), the relationship seems to be flat. For Runtime between 75 and 125, the relationship is linearly positively sloped. Finally, for Runtime more than 125 (over 2 hours), the relationship with Gross seems to be very highly variable. Furthermore, the overall relationship seems to be slightly quadratic; notice that in the GGPairs plot, the correlation between Runtime2 (power transformation with lambda = 2) and Gross is higher than the correlation between Runtime and Gross. Thus, we choose to add bins for Runtime up to 75, between 75 and 125, and over 125. Furthermore, we choose to add power transformation of Runtime2. In Question 5, we will also explore adding interactions between the binned variable and the power transformation, which seems appropriate since the relationship between Runtime and Gross does not seem to be the same within these points.
ggplot(df, aes(Runtime, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
geom_vline(xintercept=75, color='orange', linetype='longdash') +
geom_vline(xintercept=125, color='blue', linetype='longdash') +
ggtitle('Average Gross vs Runtime')

ggpairs(df[c('Runtime', 'Runtime2', 'Runtimeneg1', 'logRuntime', 'Gross')])

Finally, the last numerical column is released_year. If we plot the average gross amount by released_year we see there are actually “cycles” that appear to be forming.
ggplot(df, aes(released_year, Gross)) +
geom_point(stat='summary') +
geom_vline(xintercept=2004.5, color='orange', linetype='longdash') +
geom_vline(xintercept=2009.5, color='blue', linetype='longdash') +
ggtitle('Relationship between Gross and Released Year')
No summary function supplied, defaulting to `mean_se()

ggpairs(df[c('released_year', 'released_year2', 'released_yearneg1', 'logreleased_year', 'Gross')])

For example, in the graph above, we see that gross amounts are generally increasing between 2000 to 2004. Then drops in 2005 and generally rises again between 2005 and 2009. Then, average gross amounts drops again in 2010. This is likely reflective of the economy/business cycles. Especially the drop between 2009 to 2010 could be a reflection of the Great Recession where people spent less money on entertainment. Thus, I wanted to, instead of just using released_year as a continuous variable, create three bins of years: early 2000’s, mid 2000’s, and post 2010.
However, notice that in the GGPairs plot, none of the power transformations seemed to offer any additional linear correlation; thus I chose not to add any power transformations of released_year into the model.
Q: How did the RMSE change compared to Task 1?
A:
Below, we plot the Training and Test RMSE for Model 2. Furthermore, we can directly compare the best test RMSE below:
ggplot(data=df_eval_mod2, aes(x=train_size, y=rmses)) +
geom_line(aes(color=rmse_type, group=rmse_type)) +
ggtitle('Model 2: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
improvement_in_rmse_model2 = eval_model2$best_test_rmse - eval_model1$best_test_rmse
percentage_improvement_in_rmse_model2 = 1.0 * abs(eval_model2$best_test_rmse - eval_model1$best_test_rmse) / eval_model1$best_test_rmse
print('Improvement in RMSE')
[1] "Improvement in RMSE"
print(improvement_in_rmse_model2)
[1] -992507.5
# As a percentage
print(percentage_improvement_in_rmse_model2)
[1] 0.01134934
Notice that the improvement in model2 is -9.925075210^{5} which actually represents a 1.134934 percentage gain! While this might seem modest; remember that we did note some meaningful interactions that will be further explored in Part 5!
Also, we can look at where the best test MSE occured, as a function of training size:
# Best test RMSE
best_testrmse_mod2 = eval_model2$best_test_rmse
best_train_size_mod2 = eval_model2$best_train_size
The best mean test RMSE value I observed was 8.645817510^{7} and I observed that at the training set size 100 percent. Notice from the graph above as well that we see that Test RMSE generally decreases with higher training set. This makes sense because with very low training set size, the model will overfit the more limited data.
3. Non-numeric variables
Write code that converts genre, actors, directors, and other categorical variables to columns that can be used for regression (e.g. binary columns as you did in Project 1). Also process variables such as awards into more useful columns (again, like you did in Project 1). Now use these converted columns only to build your next model.
# Create a helper function to help one-hot encode certain text columns
# For example, Genre, Actors, Directors
# By default we will keep the top ten of each
create_one_hot_encoded = function(df, column, numTopCols=10){
# Keyword Args:
# df: DataFrame with data and column you want to one-hot-encode
# column: Column within the df that you want to one-hot encode
# Returns:
# df: Original DataFrame with newly appended columns of numTopCols highest occuring value of Column
# top_cols: The top_cols that were returned
# Example of how to one-hot encode: https://stackoverflow.com/questions/39778387/r-dataframe-one-hot-encoding-of-column-containing-multiple-terms
df = cbind(df, mtabulate(strsplit(df[[column]], ",")))
cols = unique(unlist(strsplit(df[[column]], ',')))
top_cols = sort(colSums(df[cols]), decreasing=TRUE)
top_cols_names = names(top_cols[1:numTopCols])
non_top_cols_names = setdiff(cols, top_cols_names)
# Remove the non_top_cols
df[non_top_cols_names] = list(NULL)
return(list('df'=df,
'top_cols'=top_cols_names))
}
# Processing Genre Column -- Work taken from PR1
# Note: Change 'Sci-Fi' to 'SciFi' because dashes in column names are really annoying in R
# Also changing N/A in Genre to na since / are also really annoying
df$Genre <- gsub('Sci-Fi', 'SciFi', df$Genre)
# Processing Rating Column -- Work taken from PR1
df$rated_cleaned = ifelse(df$Rated %in% c('G', 'PG-13', 'PG', 'R', 'NC-17'), df$Rated, 'Other')
df$rated_cleaned <- gsub('PG-13', 'PG13', df$rated_cleaned)
df$rated_cleaned <- gsub('NC-17', 'NC17', df$rated_cleaned)
# Preprocessing Categorical Variables to remove N/A
df$Actors <- gsub('N/A', 'na', df$Actors)
df$Country <- gsub('N/A', 'na', df$Country)
df$Director <- gsub('N/A', 'na', df$Director)
df$Genre <- gsub('N/A', 'na', df$Genre)
df$Writer <- gsub('N/A', 'na', df$Writer)
df$rated_cleaned <- gsub('N/A', 'na', df$rated_cleaned)
# Preprocessing Categorical Variables to remove spaces
df$Actors <- gsub(' ', '', df$Actors)
df$Country <- gsub(' ', '', df$Country)
df$Director <- gsub(' ', '', df$Director)
df$Genre <- gsub(' ', '', df$Genre)
df$Writer <- gsub(' ', '', df$Writer)
df$rated_cleaned <- gsub(' ', '', df$rated_cleaned)
# Preprocessing to remove parantheses
df$Writer <- gsub('\\(', '', df$Writer)
df$Writer <- gsub('\\)', '', df$Writer)
# Creating one-hot encoded Categorical columns
genre_one_hot_encoded = create_one_hot_encoded(df, 'Genre', 10)
df = genre_one_hot_encoded$df
top_genres = genre_one_hot_encoded$top_cols
actor_one_hot_encoded = create_one_hot_encoded(df, 'Actors', 20)
df = actor_one_hot_encoded$df
top_actors = actor_one_hot_encoded$top_cols
director_one_hot_encoded = create_one_hot_encoded(df, 'Director', 20)
df = director_one_hot_encoded$df
top_directors = director_one_hot_encoded$top_cols
country_one_hot_encoded = create_one_hot_encoded(df, 'Country', 10)
df = country_one_hot_encoded$df
top_countries = country_one_hot_encoded$top_cols
writer_one_hot_encoded = create_one_hot_encoded(df, 'Writer', 20)
df = writer_one_hot_encoded$df
top_writers = writer_one_hot_encoded$top_cols
rating_one_hot_encoded = create_one_hot_encoded(df, 'rated_cleaned', 5)
df = rating_one_hot_encoded$df
top_ratings = rating_one_hot_encoded$top_cols
# Remove columns from DataFrame
df$Genre = NULL
df$Director = NULL
df$Actors = NULL
df$Country = NULL
df$Rating = NULL
# Processing Awards column
# Work taken from PR1
cols = c('Title', 'Awards', 'Gross')
df6 = df[cols]
awards = stri_extract_all(df6$Awards, regex="\\d+")
award_labels = stri_extract_all(tolower(df6$Awards), regex="win|won|wins|nomin", ignore.case=TRUE)
wins_list = c()
nominations_list = c()
for (i in 1:length(awards)){
wins = 0
nominations = 0
for(j in 1:length(awards[[i]])){
if(!is.na(award_labels[[i]][j]) & (award_labels[[i]][j] == 'wins' | award_labels[[i]][j] == 'win' | award_labels[[i]][j] == 'won')){
wins = wins + as.numeric(awards[[i]][j])
} else if (!is.na(award_labels[[i]][j]) & award_labels[[i]][j] == 'nomin'){
nominations = nominations + as.numeric(awards[[i]][j])
}
}
wins_list = c(wins_list, wins)
nominations_list = c(nominations_list, nominations)
}
df$wins = wins_list
df$nominations = nominations_list
# TODO: Build & evaluate model 3 (converted non-numeric variables only)
# "Recreate" the training and test df with the same indices to include the new columns
train_df <- df[train_idx, ]
test_df <- df[-train_idx, ]
# Turning a vector of columns in a model expression: https://stackoverflow.com/questions/7986805/how-do-i-run-a-multiple-linear-regression-using-a-vector-as-my-predictors
model3_cols = c(top_genres, top_directors, top_actors, top_countries, top_writers, top_ratings, c('wins', 'nominations'))
model3_exp = paste('Gross~', paste(model3_cols, collapse='+'))
eval_model3 = eval_model(train_df, test_df, model3_exp, train_sizes, num_repeat=10)
df_eval_mod3 = eval_model3$df_eval
Q: Explain which categorical variables you used, and how you encoded them into features.
A:
The categorical variables that I used were
- Genres (top 10)
- Directors (top 20)
- Actors (top 20)
- Countries (top 10)
- Writers (top 20)
- Awards (processed into wins and nominations)
- Ratings (e.g. R, PG13, PG, G; top 5)
The reason for these categorical variables why I only use the top 10/20 is because of curse of dimensionality. If I included all as features, I would have too much variation (ala bias-variance tradeoff). To encode them into features, for each entity (e.g. genre, director, actor) I created a new one-hot encoded binary variable. Each variable would equal 1 if the movie contained that entity, else 0. Please see the helper function I defined create_one_hot_encoded for the code that I used.
Q: What is the best mean test RMSE value you observed, and at what training set size? How does this compare with Task 2?
A:
# Best test RMSE
best_testrmse_mod3 = eval_model3$best_test_rmse
best_train_size_mod3 = eval_model3$best_train_size
The best mean test RMSE value I observed was 1.277982410^{8} and I observed that at the training set size 100 percent. Notice from the graph above as well for model 3 that we see that Test RMSE generally decreases with higher training set. Again, this makes sense because with very low training set size, the model will overfit the more limited data.
We can also compare the best test RMSE directly between Models 1, 2, and 3
ggplot(data=df_eval_mod3, aes(x=train_size, y=rmses)) +
geom_line(aes(color=rmse_type, group=rmse_type)) +
ggtitle('Model 3: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
print(eval_model3$best_test_rmse)
[1] 127798239
Notice that compared to Model 2, Model 3 performed much worse. This is because we only used categorical variables for Model 3 and we have a pretty sparse data set just using categorical variables. This also potentially suggests that just using the categorical variables is not sufficient, we need to combine numerical and even interact numerical and categorical variables to come up with an even better model.
4. Numeric and categorical variables
Try to improve the prediction quality as much as possible by using both numeric and non-numeric variables from Tasks 2 & 3.
# TODO: Build & evaluate model 4 (numeric & converted non-numeric variables)
model4_cols = unique(c(model2_cols, model3_cols))
model4_exp = paste('Gross~', paste(model4_cols, collapse='+'))
eval_model4 = eval_model(train_df, test_df, model4_exp, train_sizes, num_repeat=10)
df_eval_mod4 = eval_model4$df_eval
Q: Compare the observed RMSE with Tasks 2 & 3.
A: For Model 4, I simply combined (but no additional interactions) the terms from Task 2 and 3 together. The Training vs Test RMSE for Model 4 is shown above. In addition, we can directly compare the best test RMSEs:
ggplot(data=df_eval_mod4, aes(x=train_size, y=rmses)) +
geom_line(aes(color=rmse_type, group=rmse_type)) +
ggtitle('Model 4: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
print(eval_model3$best_test_rmse)
[1] 127798239
print(eval_model4$best_test_rmse)
[1] 84986467
The improvement of Model 4 compared to Model 2 was:
improvement_in_rmse_model4 = eval_model4$best_test_rmse - eval_model2$best_test_rmse
percentage_improvement_in_rmse_model4 = 1.0 * abs(eval_model4$best_test_rmse - eval_model2$best_test_rmse) / eval_model2$best_test_rmse
print('Improvement in RMSE')
[1] "Improvement in RMSE"
print(improvement_in_rmse_model4)
[1] -1471708
# As a percentage
print(percentage_improvement_in_rmse_model4)
[1] 0.0170222
Notice that the improvement in Model 4 was -1.471708210^{6} which represents a further 1.7022198 percent improvement over Model 2! While this improvement might seem modest, again, in Part 5, we explore how to improve this even further with meaningful interactions. Furthermore, note that there is a massive improvement over Part 3 where we just had categorical variables.
# Best test RMSE
best_testrmse_mod4 = eval_model4$best_test_rmse
best_train_size_mod4 = eval_model4$best_train_size
The best mean test RMSE value I observed for Model 4 was 8.498646710^{7} and I observed that at the training set size 100 percent. Notice again, we see a similar pattern as other models. From the graph above, notice that for Model 4 that we see that Test RMSE generally decreases with higher training set. Again, this makes sense because with very low training set size, the model will overfit the more limited data.
5. Additional features
Now try creating additional features such as interactions (e.g. is_genre_comedy x is_budget_greater_than_3M) or deeper analysis of complex variables (e.g. text analysis of full-text columns like Plot).
# TODO: Build & evaluate model 5 (numeric, non-numeric and additional features)
# Creating interaction variables between variables, as explored and discussed in Part 2
df$imdbRating_greater_than_75_imdbRating = df$imdbRating_greater_than_75 * df$imdbRating
df$imdbRating_greater_than_75_imdbRating2 = df$imdbRating_greater_than_75 * df$imdbRating2
df$is_budget_greater_than_60m_budget = df$is_budget_greater_than_60m * df$Budget
df$Runtime_RuntimeUnder75 = df$Runtime * df$RuntimeUnder75
df$Runtime_RuntimeBetween75_125 = df$Runtime * df$RuntimeBetween75_125
df$Runtime_RuntimeGreater125 = df$Runtime * df$RuntimeGreater125
df$released_year_early_2000s = df$released_year * df$early_2000s
df$released_year_mid_2000s = df$released_year * df$mid_2000s
df$released_year_post_2010 = df$released_year * df$post_2010
# Creating Family Friendly variable that is G, PG, or PG13. Also adding interactions.
df$family_friendly = as.numeric(df$G + df$PG + df$PG13 >= 1)
df$family_friendly_wins = df$family_friendly * df$wins
df$family_friendly_nominations = df$family_friendly * df$nominations
# Made to DVD
df$made_to_dvd = as.numeric(!is.na(df$DVD))
# Number of Languages
df$number_of_languages = sapply(strsplit(df[['Language']], ","), length)
# "Recreate" the training and test df with the same indices to include the new columns
train_df <- df[train_idx, ]
test_df <- df[-train_idx, ]
additional_model5_cols = c('imdbRating_greater_than_75_imdbRating',
'imdbRating_greater_than_75_imdbRating2',
'is_budget_greater_than_60m_budget',
'Runtime_RuntimeUnder75',
'Runtime_RuntimeBetween75_125',
'Runtime_RuntimeGreater125',
'released_year_early_2000s',
'released_year_mid_2000s',
'released_year_post_2010',
'family_friendly',
'family_friendly_wins',
'family_friendly_nominations',
'made_to_dvd',
'number_of_languages')
model5_cols = unique(c(model4_cols, additional_model5_cols))
model5_exp = paste('Gross~', paste(model5_cols, collapse='+'))
eval_model5 = eval_model(train_df, test_df, model5_exp, train_sizes, num_repeat=10)
df_eval_mod5 = eval_model5$df_eval
Q: Explain what new features you designed and why you chose them.
A: In addition to the features from the previous parts, I created the following new interaction features. Note that the reasoning behind these interactions were already partly explored in Part 2, and I will elaborate further here
- Interaction of
imdbRating and imdbRating_greater_than_75
- Interaction of
imdbRating2 and imdbRating_greater_than_75
- Interaction of
Budget and is_budget_greater_than_60m
- Interaction of
Runtime and RuntimeUnder75, RuntimeBetween75_125, and RuntimeGreater125
- Interaction of
Runtime2 and RuntimeUnder75, RuntimeBetween75_125, and RuntimeGreater125
- Interaction of
released_year and early_2000s, mid_2000s, and post_2010
Furthermore, I explore other relationships that were entirely previously undiscussed such as
family_friendly: Defined as MPAA rating of G, PG, or PG-13
family_friendly_wins: Interaction of family_friendly and wins
family_friendly_nominations: Interaction of family_friendly and nominations.
made_to_dvd: Indicator variable for if movie was made to DVD, intuition being if made to DVD perhaps it was a successful movie and Grossed more.
number_of_languages: Parsed the Language column and count number of languages movie is made. Intuition being if multiple languages, it is an international release/success and thus Grossed more.
For the first 6 interactions, we explored these a bit in Part 2, but I’ll elaborate furhter here. Recall that in Part 2, I created a binned variable for imdbRating at 7.5. Now, I will interact this binned variable with imdbRating and imdbRating2 to capture a different relationship after 7.5 (more on this below).
If we re-look at the relationship between imdbRating and Gross, we plot the average Gross given imdbRating, and notice that the relationship seems to be linear until about 7.5. Then the average gross increases a lot. This is an interesting relationship because maybe movies below 7.5 rating are mostly not that great and so the relationship is linear. But if a movie is really really good, when the relationship between Gross and rating changes, that is a really really good movie grosses much much more. Thus, I chose to create a binned variable at imdbRating = 7.5 and interacted it with imdbRating and imdbRating2 to try to capture a different relationship after imdbRating = 7.5, where imdbRating2 is the squared term of imdbRating (since the relationship after imdbRating=7.5 looks exponential).
ggplot(df, aes(imdbRating, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point")
Warning: Removed 22 rows containing non-finite values (stat_summary).

Second, I look at the relationship between Budget and Gross. This is because if we remember back to Project 1, higher budget films tended to gross much higher. Looking at the graphs below, it seems like $60M would be an interesting bin, and I wanted to capture the interaction of the binned variable with Budget to allow for a difference in slope after $60M.
# Distribution of Gross by Budget
# Now we need to show distribution of runtime by Budget. Do a boxplot grouped by budget
df$budget_rounded = round_any(df$Budget, 10000000, f = floor)
df$budget_rounded[df$Budget >= 60000000] = 60000000
df$budget_rounded = as.character(df$budget_rounded)
df$budget_rounded[df$budget_rounded == '600000000'] = 'Over 60M'
ggplot(df, aes(as.factor(budget_rounded), Gross)) +
geom_boxplot() +
coord_flip() +
scale_x_discrete("Budget Rounded") +
ggtitle('Distribution of Gross by Budget')

ggplot(df, aes(Budget, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point")

Next, with regards to Runtime recall from Part 2 we saw an interesting relationship between the bins of pre-75, between 75 and 125, and after 125 (reproduced visual below). To capture this interaction, we interacted Runtime and Runtime2 with the binned variables.
ggplot(df, aes(Runtime, Gross)) +
stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
geom_vline(xintercept=75, color='orange', linetype='longdash') +
geom_vline(xintercept=125, color='blue', linetype='longdash') +
ggtitle('Average Gross vs Runtime')

Next, with regards to released_year recall from Part 2 we saw an interesting relationship, potentially explained by Economic business cycles. Below, we reproduce the visualization, and to allow for the model to capture this interesting effect, we interact released_year with the binned variables.
ggplot(df, aes(released_year, Gross)) +
geom_point(stat='summary') +
geom_vline(xintercept=2004.5, color='orange', linetype='longdash') +
geom_vline(xintercept=2009.5, color='blue', linetype='longdash') +
ggtitle('Relationship between Gross and Released Year')
No summary function supplied, defaulting to `mean_se()

Next, I wanted to explore MPAA Ratings more because I noticed a pretty significant difference in Gross by different MPAA Ratings. In particular, we can see from the graph below that:
ggplot(df, aes(rated_cleaned, Gross)) +
geom_bar(stat='summary')
No summary function supplied, defaulting to `mean_se()

It looks like G, PG, and PG13 rated movies gross much higher on average than other MPAA rated movies. This makes sense as these types of movies are more “family friendly” and can relate to a much bigger audience. Thus, I created a binary variable family_friendly and interacted it with wins and nominations to see if there was any meaningful interactions. If we look at the ggpairs, it looks promising.
# Plotting Correlation Plots of the different Review number Columns:
ggpairs(df[c('family_friendly', 'wins', 'family_friendly_wins', 'Gross')])

ggpairs(df[c('family_friendly', 'nominations', 'family_friendly_nominations', 'Gross')])

Among other things from the ggpairs graph above, you can see that the correlation between Gross vs. wins and nominations is fairly high (approximately 0.3 for each), but interacted with family_friendly, the correlation goes up to almost 0.5! Thus, it seems like there is a meaningful interaction between wins, nominations and family_friendly. This essentially means that for family friendly movies, winning and/or being nominated for an award has functionally different relationship with Gross than non-family friendly movies.
Q: Comment on the final RMSE values you obtained, and what you learned through the course of this project.
A: For Model 5, we can directly compare the best test RMSEs:
ggplot(data=df_eval_mod5, aes(x=train_size, y=rmses)) +
geom_line(aes(color=rmse_type, group=rmse_type)) +
ggtitle('Model 5: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
print(eval_model3$best_test_rmse)
[1] 127798239
print(eval_model4$best_test_rmse)
[1] 84986467
print(eval_model5$best_test_rmse)
[1] 83328591
The improvement of Model 5 compared to Model 4 was:
improvement_in_rmse_model5 = eval_model5$best_test_rmse - eval_model4$best_test_rmse
percentage_improvement_in_rmse_model5 = 1.0 * abs(eval_model5$best_test_rmse - eval_model4$best_test_rmse) / eval_model4$best_test_rmse
print('Improvement in RMSE')
[1] "Improvement in RMSE"
print(improvement_in_rmse_model5)
[1] -1657875
# As a percentage
print(percentage_improvement_in_rmse_model5)
[1] 0.01950752
Notice that the improvement in Model 5 was -1.471708210^{6} which represents a further 1.9507523 percent improvement over Model 4! In addition, notice that the all-in improvement of Model 5 compared to Model 1 is -4.122091110^{6} which represents a 4.713618 percentage improvement!
# Best test RMSE
best_testrmse_mod5 = eval_model5$best_test_rmse
best_train_size_mod5 = eval_model5$best_train_size
The best mean test RMSE value I observed for Model 5 was 8.332859110^{7} and I observed that at the training set size 100 percent. Notice again, we see a similar pattern as other models. From the graph above, notice that for Model 5 that we see that Test RMSE generally decreases with higher training set. Again, this makes sense because with very low training set size, the model will overfit the more limited data.
This project was immensely useful in getting experience into digging deeper about building models. Even with this relatively limited data set; on the order of 1000s of observations and 100s of features, there is practically infinite ways we can engineer new features and thus do feature selection as to what we want to put into our models.
One thing I definitely learned was that feature engineering is useful. Especially with linear regression, the relationship between an outcome variable and potential features is very rarely truly linear. Exploring which transformations and interactions improve the model is important. However, this is not riskless!!! Because of curse of dimensionality and bias/variance tradeoff, you cannot just put whatever you want into the model. Thus, the hold-out test set is important.
Another thing that I learned is the importance of training/test split, and I found the relationship with size of training set interesting. The test error seemed to generally decrease and generally the lowest test error was found when trained on 100% of training set. This is a key lesson since if you train on small sample size, the model will overfit the small training set (even with a relatively inflexible method like linear regression!) Thus, always be careful of overfitting and never trust the training error.
If I wanted to take this a step forward, I would definitely want to include some kind of validation set/procedure (e.g. K-fold validation). However, given the scope of this project assignment and this Piazza post: https://piazza.com/class/j6gt7ycx6nk145?cid=1123, I did not do so here. However, this would be one tool to help us figure out the best features to include in our model. For example, this would be a good tool to help us figure out what power transformations to use.
LS0tCnRpdGxlOiAnUHJvamVjdCAyOiBNb2RlbGluZyBhbmQgRXZhbHVhdGlvbicKc3VidGl0bGU6ICJDU0U2MjQyIC0gRGF0YSBhbmQgVmlzdWFsIEFuYWx5dGljcyAtIEZhbGwgMjAxN1xuXG5EdWU6IFN1bmRheSwgTm92ZW1iZXIgMjYsIDIwMTcgYXQgMTE6NTkgUE0gVVRDLTEyOjAwIG9uIFQtU3F1YXJlIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogbm9uZQogICAgdGhlbWU6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiBub25lCiAgICB0aGVtZTogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgRGF0YQoKV2Ugd2lsbCB1c2UgdGhlIHNhbWUgZGF0YXNldCBhcyBQcm9qZWN0IDE6IFtgbW92aWVzX21lcmdlZGBdKGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9jb250ZW50LnVkYWNpdHktZGF0YS5jb20vY291cnNlcy9ndC1jczYyNDIvcHJvamVjdC9tb3ZpZXNfbWVyZ2VkKS4KCiMgT2JqZWN0aXZlCgpZb3VyIGdvYWwgaW4gdGhpcyBwcm9qZWN0IGlzIHRvIGJ1aWxkIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdGhhdCBjYW4gcHJlZGljdCB0aGUgYEdyb3NzYCByZXZlbnVlIGVhcm5lZCBieSBhIG1vdmllIGJhc2VkIG9uIG90aGVyIHZhcmlhYmxlcy4gWW91IG1heSB1c2UgUiBwYWNrYWdlcyB0byBmaXQgYW5kIGV2YWx1YXRlIGEgcmVncmVzc2lvbiBtb2RlbCAobm8gbmVlZCB0byBpbXBsZW1lbnQgcmVncmVzc2lvbiB5b3Vyc2VsZikuIFBsZWFzZSBzdGljayB0byBsaW5lYXIgcmVncmVzc2lvbiwgaG93ZXZlci4KCiMgSW5zdHJ1Y3Rpb25zCgpZb3Ugc2hvdWxkIGJlIGZhbWlsaWFyIHdpdGggdXNpbmcgYW4gW1JNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2sgYnkgbm93LiBSZW1lbWJlciB0aGF0IHlvdSBoYXZlIHRvIG9wZW4gaXQgaW4gUlN0dWRpbywgYW5kIHlvdSBjYW4gcnVuIGNvZGUgY2h1bmtzIGJ5IHByZXNzaW5nICpDbWQrU2hpZnQrRW50ZXIqLgoKUGxlYXNlIGNvbXBsZXRlIHRoZSB0YXNrcyBiZWxvdyBhbmQgc3VibWl0IHRoaXMgUiBNYXJrZG93biBmaWxlIChhcyAqKnByMi5SbWQqKikgY29udGFpbmluZyBhbGwgY29tcGxldGVkIGNvZGUgY2h1bmtzIGFuZCB3cml0dGVuIHJlc3BvbnNlcywgYW5kIGEgUERGIGV4cG9ydCBvZiBpdCAoYXMgKipwcjIucGRmKiopIHdoaWNoIHNob3VsZCBpbmNsdWRlIHRoZSBvdXRwdXRzIGFuZCBwbG90cyBhcyB3ZWxsLgoKX05vdGUgdGhhdCAqKlNldHVwKiogYW5kICoqRGF0YSBQcmVwcm9jZXNzaW5nKiogc3RlcHMgZG8gbm90IGNhcnJ5IGFueSBwb2ludHMsIGhvd2V2ZXIsIHRoZXkgbmVlZCB0byBiZSBjb21wbGV0ZWQgYXMgaW5zdHJ1Y3RlZCBpbiBvcmRlciB0byBnZXQgbWVhbmluZ2Z1bCByZXN1bHRzLl8KCiMgU2V0dXAKClNhbWUgYXMgUHJvamVjdCAxLCBsb2FkIHRoZSBkYXRhc2V0IGludG8gbWVtb3J5OgoKYGBge3J9CnNldHdkKCJ+L2dpdC9HZW9yZ2lhVGVjaC9jc2U2MjQyL3ByMiIpCmxvYWQoJ21vdmllc19tZXJnZWQnKQpgYGAKClRoaXMgY3JlYXRlcyBhbiBvYmplY3Qgb2YgdGhlIHNhbWUgbmFtZSAoYG1vdmllc19tZXJnZWRgKS4gRm9yIGNvbnZlbmllbmNlLCB5b3UgY2FuIGNvcHkgaXQgdG8gYGRmYCBhbmQgc3RhcnQgdXNpbmcgaXQ6CgpgYGB7cn0KZGYgPSBtb3ZpZXNfbWVyZ2VkCmNhdCgiRGF0YXNldCBoYXMiLCBkaW0oZGYpWzFdLCAicm93cyBhbmQiLCBkaW0oZGYpWzJdLCAiY29sdW1ucyIsIGVuZD0iXG4iLCBmaWxlPSIiKQpjb2xuYW1lcyhkZikKYGBgCgojIyBMb2FkIFIgcGFja2FnZXMKCkxvYWQgYW55IFIgcGFja2FnZXMgdGhhdCB5b3Ugd2lsbCBuZWVkIHRvIHVzZS4gWW91IGNhbiBjb21lIGJhY2sgdG8gdGhpcyBjaHVuaywgZWRpdCBpdCBhbmQgcmUtcnVuIHRvIGxvYWQgYW55IGFkZGl0aW9uYWwgcGFja2FnZXMgbGF0ZXIuCgpgYGB7cn0KbGlicmFyeShHR2FsbHkpICAjIFVzZWQgZm9yIGdncGFpcnMKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICMgVXNlZCB0byBleHRyYWN0IHllYXIgZnJvbSBSZWxlYXNlZApsaWJyYXJ5KHFkYXBUb29scykgICMgVXNlZCBmb3IgbXRhYnVsYXRlICh0byBvbmUtaG90IGVuY29kZSBhIGNvbHVtbikKbGlicmFyeShyZXNoYXBlKSAgIyBVc2VkIGZvciBtZWx0IGZ1bmN0aW9uCmxpYnJhcnkoc3RyaW5naSkgICMgVXNlZCBmb3IgcHJvY2Vzc2luZyBBd2FyZHMKYGBgCgpJZiB5b3UgYXJlIHVzaW5nIGFueSBub24tc3RhbmRhcmQgcGFja2FnZXMgKG9uZXMgdGhhdCBoYXZlIG5vdCBiZWVuIGRpc2N1c3NlZCBpbiBjbGFzcyBvciBleHBsaWNpdGx5IGFsbG93ZWQgZm9yIHRoaXMgcHJvamVjdCksIHBsZWFzZSBtZW50aW9uIHRoZW0gYmVsb3cuIEluY2x1ZGUgYW55IHNwZWNpYWwgaW5zdHJ1Y3Rpb25zIGlmIHRoZXkgY2Fubm90IGJlIGluc3RhbGxlZCB1c2luZyB0aGUgcmVndWxhciBgaW5zdGFsbC5wYWNrYWdlcygnPHBrZyBuYW1lPicpYCBjb21tYW5kLgoKKipOb24tc3RhbmRhcmQgcGFja2FnZXMgdXNlZCoqOiBOb25lCgojIERhdGEgUHJlcHJvY2Vzc2luZwoKQmVmb3JlIHdlIHN0YXJ0IGJ1aWxkaW5nIG1vZGVscywgd2Ugc2hvdWxkIGNsZWFuIHVwIHRoZSBkYXRhc2V0IGFuZCBwZXJmb3JtIGFueSBwcmVwcm9jZXNzaW5nIHN0ZXBzIHRoYXQgbWF5IGJlIG5lY2Vzc2FyeS4gU29tZSBvZiB0aGVzZSBzdGVwcyBjYW4gYmUgY29waWVkIGluIGZyb20geW91ciBQcm9qZWN0IDEgc29sdXRpb24uIEl0IG1heSBiZSBoZWxwZnVsIHRvIHByaW50IHRoZSBkaW1lbnNpb25zIG9mIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lIGF0IGVhY2ggc3RlcC4KCiMjIDEuIFJlbW92ZSBub24tbW92aWUgcm93cwoKYGBge3J9CiMgVE9ETzogUmVtb3ZlIGFsbCByb3dzIGZyb20gZGYgdGhhdCBkbyBub3QgY29ycmVzcG9uZCB0byBtb3ZpZXMKZGYgPC0gZGZbZGYkVHlwZSA9PSAibW92aWUiLF0KYGBgCgojIyAyLiBEcm9wIHJvd3Mgd2l0aCBtaXNzaW5nIGBHcm9zc2AgdmFsdWUKClNpbmNlIG91ciBnb2FsIGlzIHRvIG1vZGVsIGBHcm9zc2AgcmV2ZW51ZSBhZ2FpbnN0IG90aGVyIHZhcmlhYmxlcywgcm93cyB0aGF0IGhhdmUgbWlzc2luZyBgR3Jvc3NgIHZhbHVlcyBhcmUgbm90IHVzZWZ1bCB0byB1cy4KCmBgYHtyfQojIFRPRE86IFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyBHcm9zcyB2YWx1ZQpkZiA9IGRmW3doaWNoKCFpcy5uYShkZiRHcm9zcykpLCBdCmBgYAoKIyMgMy4gRXhjbHVkZSBtb3ZpZXMgcmVsZWFzZWQgcHJpb3IgdG8gMjAwMAoKSW5mbGF0aW9uIGFuZCBvdGhlciBnbG9iYWwgZmluYW5jaWFsIGZhY3RvcnMgbWF5IGFmZmVjdCB0aGUgcmV2ZW51ZSBlYXJuZWQgYnkgbW92aWVzIGR1cmluZyBjZXJ0YWluIHBlcmlvZHMgb2YgdGltZS4gVGFraW5nIHRoYXQgaW50byBhY2NvdW50IGlzIG91dCBvZiBzY29wZSBmb3IgdGhpcyBwcm9qZWN0LCBzbyBsZXQncyBleGNsdWRlIGFsbCBtb3ZpZXMgdGhhdCB3ZXJlIHJlbGVhc2VkIHByaW9yIHRvIHRoZSB5ZWFyIDIwMDAgKHlvdSBtYXkgdXNlIGBSZWxlYXNlZGAsIGBEYXRlYCBvciBgWWVhcmAgZm9yIHRoaXMgcHVycG9zZSkuCgpgYGB7cn0KIyBUT0RPOiBFeGNsdWRlIG1vdmllcyByZWxlYXNlZCBwcmlvciB0byAyMDAwCmRmJHJlbGVhc2VkX3llYXIgPSB5ZWFyKGRmJFJlbGVhc2VkKQpkZiA9IGRmW3doaWNoKGRmJHJlbGVhc2VkX3llYXIgPj0gMjAwMCksIF0KYGBgCgojIyA0LiBFbGltaW5hdGUgbWlzbWF0Y2hlZCByb3dzCgpfTm90ZTogWW91IG1heSBjb21wYXJlIHRoZSBgUmVsZWFzZWRgIGNvbHVtbiAoc3RyaW5nIHJlcHJlc2VudGF0aW9uIG9mIHJlbGVhc2UgZGF0ZSkgd2l0aCBlaXRoZXIgYFllYXJgIG9yIGBEYXRlYCAobnVtZXJpYyByZXByZXNlbnRhdGlvbiBvZiB0aGUgeWVhcikgdG8gZmluZCBtaXNtYXRjaGVzLiBUaGUgZ29hbCBpcyB0byBhdm9pZCByZW1vdmluZyBtb3JlIHRoYW4gMTAlIG9mIHRoZSByb3dzLl8KCmBgYHtyfQojIFRPRE86IFJlbW92ZSBtaXNtYXRjaGVkIHJvd3MKIyBUaGlzIGlzIGp1c3QgdGFrZW4gZnJvbSBteSB3b3JrIGluIHByMTsgb25seSBkcm9wIGlmIHRoZSBkaWZmZXJlbmNlIGluIHRoZSB5ZWFycyBpcyAyIG9yIGdyZWF0ZXIuCmRmJG5vdF9tYXRjaGVkID0gKGFicyhkZiRZZWFyICE9IGRmJHJlbGVhc2VkX3llYXIpID49IDIpCgojIE51bWJlciBvZiByb3dzIHdpdGggbm9uLW51bGwgR3Jvc3MgVmFsdWUKcHJpbnQobnJvdyhkZikpCnByaW50KG5yb3coZGZbd2hpY2goIWlzLm5hKGRmJEdyb3NzKSksIF0pKQoKIyBSZW1vdmUgbWlzbWF0Y2hlZCByb3dzCmRmID0gZGZbd2hpY2goIShkZiRub3RfbWF0Y2hlZCA9PSBUUlVFKSksIF0KcHJpbnQobnJvdyhkZikpCmBgYAoKIyMgNS4gRHJvcCBgRG9tZXN0aWNfR3Jvc3NgIGNvbHVtbgoKYERvbWVzdGljX0dyb3NzYCBpcyBiYXNpY2FsbHkgdGhlIGFtb3VudCBvZiByZXZlbnVlIGEgbW92aWUgZWFybmVkIHdpdGhpbiB0aGUgVVMuIFVuZGVyc3RhbmRhYmx5LCBpdCBpcyB2ZXJ5IGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggYEdyb3NzYCBhbmQgaXMgaW4gZmFjdCBlcXVhbCB0byBpdCBmb3IgbW92aWVzIHRoYXQgd2VyZSBub3QgcmVsZWFzZWQgZ2xvYmFsbHkuIEhlbmNlLCBpdCBzaG91bGQgYmUgcmVtb3ZlZCBmb3IgbW9kZWxpbmcgcHVycG9zZXMuCgpgYGB7cn0KIyBUT0RPOiBFeGNsdWRlIHRoZSBgRG9tZXN0aWNfR3Jvc3NgIGNvbHVtbgpkZiREb21lc3RpY19Hcm9zcyA9IE5VTEwKYGBgCgojIyA2LiBQcm9jZXNzIGBSdW50aW1lYCBjb2x1bW4KCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiMgVE9ETzogUmVwbGFjZSBkZiRSdW50aW1lIHdpdGggYSBudW1lcmljIGNvbHVtbiBjb250YWluaW5nIHRoZSBydW50aW1lIGluIG1pbnV0ZXMKIyBUYWtpbmcgd29yayBmcm9tIHByMQpleHRyYWN0X3J1bnRpbWUgPSBmdW5jdGlvbihyKXsKICAgIHRpbWVzID0gdW5saXN0KHIpCiAgICBtaW51dGVzID0gMAogICAgZm9yIChpIGluIDE6bGVuZ3RoKHRpbWVzKSAtIDEpewogICAgICAgIGlmICh0aW1lc1tpICsgMV0gPT0gJ2gnKXsKICAgICAgICAgICAgbWludXRlcyA9IG1pbnV0ZXMgKyBhcy5udW1lcmljKHRpbWVzW2ldKSAqIDYwCiAgICAgICAgfSBlbHNlIGlmICh0aW1lc1tpICsgMV0gPT0gJ21pbicpewogICAgICAgICAgICBtaW51dGVzID0gbWludXRlcyArIGFzLm51bWVyaWModGltZXNbaV0pCiAgICAgICAgfQogICAgfQogICAgaWYgKG1pbnV0ZXMgPT0gMCl7CiAgICAgICAgcmV0dXJuKE5BKQogICAgfSBlbHNlewogICAgICAgIHJldHVybihtaW51dGVzKQogICAgfQp9Cgp5PXN0cnNwbGl0KGRmJFJ1bnRpbWUsJyAnKQpuZXdfcnVudGltZXMgPSB1bmxpc3QobGFwcGx5KHksIGV4dHJhY3RfcnVudGltZSkpCmRmJFJ1bnRpbWUgPSBuZXdfcnVudGltZXMKYGBgCgpQZXJmb3JtIGFueSBhZGRpdGlvbmFsIHByZXByb2Nlc3Npbmcgc3RlcHMgdGhhdCB5b3UgZmluZCBuZWNlc3NhcnksIHN1Y2ggYXMgZGVhbGluZyB3aXRoIG1pc3NpbmcgdmFsdWVzIG9yIGhpZ2hseSBjb3JyZWxhdGVkIGNvbHVtbnMgKGZlZWwgZnJlZSB0byBhZGQgbW9yZSBjb2RlIGNodW5rcywgbWFya2Rvd24gYmxvY2tzIGFuZCBwbG90cyBoZXJlIGFzIG5lY2Vzc2FyeSkuCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQojIFRPRE8ob3B0aW9uYWwpOiBBZGRpdGlvbmFsIHByZXByb2Nlc3NpbmcKCiMgUGxvdHRpbmcgQ29ycmVsYXRpb24gUGxvdHMgb2YgdGhlIGRpZmZlcmVudCBSYXRpbmcgQ29sdW1uczoKZ2dwYWlycyhkZltjKCdpbWRiUmF0aW5nJywgJ3RvbWF0b1JhdGluZycsICd0b21hdG9Vc2VyUmF0aW5nJywgJ3RvbWF0b01ldGVyJyldKQoKIyBQbG90dGluZyBDb3JyZWxhdGlvbiBQbG90cyBvZiB0aGUgZGlmZmVyZW50IFJldmlldyBudW1iZXIgQ29sdW1uczoKZ2dwYWlycyhkZltjKCdpbWRiVm90ZXMnLCAndG9tYXRvUmV2aWV3cycsICd0b21hdG9Vc2VyUmV2aWV3cycpXSkKCiMgUmVtb3ZlIHRvbWF0by1saWtlIENvbHVtbnMgdG8gYXZvaWQgaGlnaCB2YXJpYW5jZSBpbiBtb2RlbHMuCmRmJHRvbWF0b01ldGVyID0gTlVMTApkZiR0b21hdG9SYXRpbmcgPSBOVUxMCmRmJHRvbWF0b1Jldmlld3MgPSBOVUxMCmRmJHRvbWF0b0ZyZXNoID0gTlVMTApkZiR0b21hdG9Sb3R0ZW4gPSBOVUxMCmRmJHRvbWF0b1VzZXJNZXRlciA9IE5VTEwKZGYkdG9tYXRvVXNlclJhdGluZyA9IE5VTEwKZGYkdG9tYXRvVXNlclJldmlld3MgPSBOVUxMCgojIFJlbW92ZSBZZWFyIHNpbmNlIHdlIHdpbGwgdXNlIHJlbGVhc2VkX3llYXIgY29sdW1uCmRmJFllYXIgPSBOVUxMCgojIEFsc28gY3JlYXRpbmcgY2F0ZWdvcmljYWwgdmFsdWUgZm9yIHJ1bnRpbWUsIGp1c3QgYXMgd2UgZGlkIGluIFByb2plY3QgMSB0byBleHBsb3JlIHJlbGF0aW9uc2hpcHMKZGYkcnVudGltZV9jYXRlZ29yaWNhbCA9IGlmZWxzZShkZiRSdW50aW1lIDw9IDY4LCAnc2hvcnQnLCBpZmVsc2UoZGYkUnVudGltZSA8PSAxMDEsICdtZWRpdW0nLCAnbG9uZycpKQpgYGAKCkZyb20gdGhlIGFib3ZlLCBub3RpY2UgdGhhdCBgaW1kYlJhdGluZ2AgaXMgdmVyeSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIGB0b21hdG9SYXRpbmdgLCBgdG9tYXRvVXNlclJhdGluZ2AgYW5kIGB0b21hdG9NZXRlcmAuIFRodXMsIHRvIGF2b2lkIGhpZ2ggdmFyaWFuY2UgaW4gdGhlIG1vZGVscywgaXQgbWFrZXMgbW9yZSBzZW5zZSB0byBqdXN0IGluY2x1ZGUgYGltZGJSYXRpbmdgIGFzIGEgcHJveHkgZm9yIHRoZSByYXRpbmcgb2YgdGhlIG1vdmllLiAKCldoZW4gcGxvdHRpbmcgYSBzaW1pbGFyIHBsb3QgZm9yIHVzZXIgcmV2aWV3cywgaXQgYWdhaW4sIGl0IGxvb2tzIGxpa2UgYGltZGJWb3Rlc2AgaXMgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBgdG9tYXRvUmV2aWV3c2AuIEZ1cnRoZXJtb3JlLCB3aGlsZSBgaW1kYlZvdGVzYCBpcyBub3QgYXMgY29ycmVsYXRlZCB3aXRoIGB0b21hdG9Vc2VyUmV2aWV3c2AsIHRoZXJlIGRvZXMgc2VlbSB0byBiZSBzdGlsbCBzdWZmaWNpZW50IGxpbmVhciBjb3JyZWxhdGlvbi4gQmFzZWQgb24gdGhlc2UgdHdvIHBsb3RzLCBpbiB0aGlzIGFzc2lnbm1lbnQsIHdlJ3JlIGdvaW5nIHRvIHJlbW92ZSBhbGwgdGhlICd0b21hdG8nIGNvbHVtbnMgdG8gYXZvaWQgdW5uZWNlc3NhcnkgcG90ZW50aWFsbHkgaGlnaCB2YXJpYW5jZSBpbiBtb2RlbHMgKGFsYSBiaWFzLXZhcmlhbmNlIHRyYWRlb2ZmKS4gSXQgc2VlbXMgdGhhdCBib3RoIGBpbWRiUmF0aW5nYCBhbmQgYGltZGJWb3Rlc2AgY2FwdHVyZSBlbm91Z2ggaW5mb3JtYXRpb24gYWxvbmcgdGhlc2UgZGltZW5zaW9ucy4KCldlIHdpbGwgYWxzbyByZW1vdmUgbW92aWVzIHJlbGVhc2VkIGluIDIwMTcuIE5vdGljZSB0aGF0IGlmIHdlIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtb3ZpZXMgYnkgeWVhcjoKCmBgYHtyfQp0YWJsZShkZiRyZWxlYXNlZF95ZWFyKQpgYGAKCm5vdGljZSB0aGF0IG9ubHkgNCBtb3ZpZXMgd2VyZSByZWxlYXNlZCBpbiAyMDE3IGluIG91ciBkYXRhIHNldC4gVGhpcyBpcyBwcm9iYWJseSBqdXN0IGEgY29uc2VxdWVuY2Ugb2Ygd2hlbiB0aGlzIGRhdGEgd2FzIHB1bGxlZC4gSXQgcHJvYmFibHkgaXMgbm90IHJlcHJlc2VudGF0aXZlIHRvIGluY2x1ZGUgdGhpcyBkYXRhLCBzaW5jZSB0aGlzIGlzIG5vdCBhY3R1YWxseSByZXByZXNlbnRhdGl2ZSBvZiBtb3ZpZXMgaW4gMjAxNy4gVGh1cywgd2Ugd2lsbCByZW1vdmUgdGhlc2UgNCBvYnNlcnZhdGlvbnMKCmBgYHtyfQpkZiA9IGRmW3doaWNoKGRmJHJlbGVhc2VkX3llYXIgPCAyMDE3KSwgXQpgYGAKCl8qKk5vdGUqKjogRG8gTk9UIGNvbnZlcnQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIChsaWtlIGBHZW5yZWApIGludG8gYmluYXJ5IGNvbHVtbnMgeWV0LiBZb3Ugd2lsbCBkbyB0aGF0IGxhdGVyIGFzIHBhcnQgb2YgYSBtb2RlbCBpbXByb3ZlbWVudCB0YXNrLl8KCiMjIEZpbmFsIHByZXByb2Nlc3NlZCBkYXRhc2V0CgpSZXBvcnQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIHByZXByb2Nlc3NlZCBkYXRhc2V0IHlvdSB3aWxsIGJlIHVzaW5nIGZvciBtb2RlbGluZyBhbmQgZXZhbHVhdGlvbiwgYW5kIHByaW50IGFsbCB0aGUgZmluYWwgY29sdW1uIG5hbWVzLiAoQWdhaW4sIGBEb21lc3RpY19Hcm9zc2Agc2hvdWxkIG5vdCBiZSBpbiB0aGlzIGxpc3QhKQoKYGBge3J9CiMgVE9ETzogUHJpbnQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIGZpbmFsIHByZXByb2Nlc3NlZCBkYXRhc2V0IGFuZCBjb2x1bW4gbmFtZXMKY2F0KCJEYXRhc2V0IGhhcyIsIGRpbShkZilbMV0sICJyb3dzIGFuZCIsIGRpbShkZilbMl0sICJjb2x1bW5zIiwgZW5kPSJcbiIsIGZpbGU9IiIpCmNvbG5hbWVzKGRmKQpgYGAKCiMgRXZhbHVhdGlvbiBTdHJhdGVneQoKSW4gZWFjaCBvZiB0aGUgdGFza3MgZGVzY3JpYmVkIGluIHRoZSBuZXh0IHNlY3Rpb24sIHlvdSB3aWxsIGJ1aWxkIGEgcmVncmVzc2lvbiBtb2RlbC4gSW4gb3JkZXIgdG8gY29tcGFyZSB0aGVpciBwZXJmb3JtYW5jZSwgeW91IHdpbGwgY29tcHV0ZSB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IgKFJNU0UpIGF0IGRpZmZlcmVudCB0cmFpbmluZyBzZXQgc2l6ZXMuCgpGaXJzdCwgcmFuZG9tbHkgc2FtcGxlIDEwLTIwJSBvZiB0aGUgcHJlcHJvY2Vzc2VkIGRhdGFzZXQgYW5kIGtlZXAgdGhhdCBhc2lkZSBhcyB0aGUgKip0ZXN0IHNldCoqLiBEbyBub3QgdXNlIHRoZXNlIHJvd3MgZm9yIHRyYWluaW5nISBUaGUgcmVtYWluZGVyIG9mIHRoZSBwcmVwcm9jZXNzZWQgZGF0YXNldCBpcyB5b3VyICoqdHJhaW5pbmcgZGF0YSoqLgoKTm93IHVzZSB0aGUgZm9sbG93aW5nIGV2YWx1YXRpb24gcHJvY2VkdXJlIGZvciBlYWNoIG1vZGVsOgoKLSBDaG9vc2UgYSBzdWl0YWJsZSBzZXF1ZW5jZSBvZiB0cmFpbmluZyBzZXQgc2l6ZXMsIGUuZy4gMTAlLCAyMCUsIDMwJSwgLi4uLCAxMDAlICgxMC0yMCBkaWZmZXJlbnQgc2l6ZXMgc2hvdWxkIHN1ZmZpY2UpLiBGb3IgZWFjaCBzaXplLCBzYW1wbGUgdGhhdCBtYW55IGlucHV0cyBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLCB0cmFpbiB5b3VyIG1vZGVsLCBhbmQgY29tcHV0ZSB0aGUgcmVzdWx0aW5nIHRyYWluaW5nIGFuZCB0ZXN0IFJNU0UuCi0gUmVwZWF0IHlvdXIgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb24gYXQgbGVhc3QgMTAgdGltZXMgYXQgZWFjaCB0cmFpbmluZyBzZXQgc2l6ZSwgYW5kIGF2ZXJhZ2UgdGhlIFJNU0UgcmVzdWx0cyBmb3Igc3RhYmlsaXR5LgotIEdlbmVyYXRlIGEgZ3JhcGggb2YgdGhlIGF2ZXJhZ2VkIHRyYWluIGFuZCB0ZXN0IFJNU0UgdmFsdWVzIGFzIGEgZnVuY3Rpb24gb2YgdGhlIHRyYWluIHNldCBzaXplICglKSwgd2l0aCBvcHRpb25hbCBlcnJvciBiYXJzLgoKWW91IGNhbiBkZWZpbmUgYSBoZWxwZXIgZnVuY3Rpb24gdGhhdCBhcHBsaWVzIHRoaXMgcHJvY2VkdXJlIHRvIGEgZ2l2ZW4gc2V0IG9mIGZlYXR1cmVzIGFuZCByZXVzZSBpdC4KCmBgYHtyfQojIFNldHRpbmcgdXAgVHJhaW5pbmcgYW5kIFRlc3QgREYKc3BsaXRfdHJhaW5fdGVzdF9kZiA9IGZ1bmN0aW9uKGRmLCB0cmFpbl9zcGxpdD0wLjgpewogICMgR2l2ZW4gYSBEYXRhRnJhbWUsIHJhbmRvbWx5IHNwbGl0IHRvIHRyYWluaW5nIGFuZCB0ZXN0IERhdGFGcmFtZQogICMgTm93IFNlbGVjdGluZyA4MCUgb2YgZGF0YSBhcyBzYW1wbGUgZnJvbSB0b3RhbCAnbicgcm93cyBvZiB0aGUgZGF0YQogIHNldC5zZWVkKDEwMCkKICBzYW1wbGUgPC0gc2FtcGxlLmludChuID0gbnJvdyhkZiksIHNpemUgPSBmbG9vciguOCpucm93KGRmKSksIHJlcGxhY2UgPSBGKQogIHRyYWluX2RmIDwtIGRmW3NhbXBsZSwgXQogIHRlc3RfZGYgIDwtIGRmWy1zYW1wbGUsIF0KICByZXR1cm4obGlzdCgndHJhaW5fZGYnPXRyYWluX2RmLAogICAgICAgICAgICAgICd0ZXN0X2RmJz10ZXN0X2RmLAogICAgICAgICAgICAgICd0cmFpbl9pZHgnPXNhbXBsZSkpCn0KCnNwbGl0X2RmID0gc3BsaXRfdHJhaW5fdGVzdF9kZihkZiwgdHJhaW5fc3BsaXQ9MC44KQp0cmFpbl9kZiA9IHNwbGl0X2RmJHRyYWluX2RmCnRlc3RfZGYgPSBzcGxpdF9kZiR0ZXN0X2RmCnRyYWluX2lkeCA9IHNwbGl0X2RmJHRyYWluX2lkeAoKIyBDcmVhdGUgdHJhaW5pbmcgc2V0IHNpemUgdmVjdG9yCnRyYWluX3NpemVzID0gYygwLjEsIDAuMiwgMC4zLCAwLjQsIDAuNSwgMC42LCAwLjcsIDAuOCwgMC45LCAxKQpgYGAKCmBgYHtyfQojIEhlbHBlciBGdW5jdGlvbnMKZ2V0cm1zZSA9IGZ1bmN0aW9uKG1vZCwgdGVzdF9kZil7CiAgICAjIEhlbHBlciBmdW5jdGlvbiB0byByZXR1cm4gYm90aCB0aGUgdHJhaW5pbmcgUk1TRSBhbmQgdGVzdCBSTVNFIGdpdmVuIG1vZGVsIGFuZCB0ZXN0IERhdGFGcmFtZQogICAgIyBGcm9tIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQzMTIzNDYyL2hvdy10by1vYnRhaW4tcm1zZS1vdXQtb2YtbG0tcmVzdWx0CiAgICB0cmFpbl9yc3MgPSBjKGNyb3NzcHJvZChtb2QkcmVzaWR1YWxzKSkKICAgIHRyYWluX21zZSA9IHRyYWluX3JzcyAvIGxlbmd0aChtb2QkcmVzaWR1YWxzKQogICAgdHJhaW5fcm1zZSA9IHNxcnQodHJhaW5fbXNlKQogICAgCiAgICB0ZXN0X3Jtc2UgPSBzcXJ0KG1lYW4oKHRlc3RfZGYkR3Jvc3MgLSBwcmVkaWN0LmxtKG1vZCwgdGVzdF9kZikpIF4gMiwgbmEucm09VFJVRSkpCiAgICAKICAgIHJldHVybihsaXN0KCd0cmFpbl9ybXNlJz10cmFpbl9ybXNlLAogICAgICAgICAgICAgICAgJ3Rlc3Rfcm1zZSc9dGVzdF9ybXNlKSkKfQpgYGAKCmBgYHtyfQojIEhlbHBlciBmdW5jdGlvbiB0byBidWlsZCBhbmQgZXZhbHVhdGUgbW9kZWwgYWNjb3JkaW5nIHRvIHRoZSBpbnN0cnVjdGlvbnMgcHJvdmlkZWQKZXZhbF9tb2RlbCA9IGZ1bmN0aW9uKHRyYWluX2RmLCB0ZXN0X2RmLCBtb2RlbF9leHAsIHRyYWluX3NpemVzLCBudW1fcmVwZWF0PTEwKXsKICAjIEtleXdvcmQgQXJnczoKICAjICB0cmFpbl9kZjogVHJhaW5pbmcgRGF0YUZyYW1lCiAgIyAgdGVzdF9kZjogVGVzdCBEYXRhRnJhbWUKICAjICBtb2RlbF9leHA6IEV4cHJlc3Npb24gb2YgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgYXMgYSBzdHJpbmcKICAjICBudW1fcmVwZWF0OiBOdW1iZXIgb2YgdGltZXMgdG8gcmVwZWF0IGV2YWx1YXRpbmcgdGhlIG1vZGVsIChzaW5jZSB0aGVyZSBtaWdodCBiZSBoaWdoIHZhcmlhbmNlKQogICMgUmV0dXJuczogZGZfZXZhbCB3aGljaCBpcyBhIERhdGFGcmFtZSB3aXRoIHRlc3QgYW5kIHRyYWluIHJtc2UKICB0cmFpbl9ybXNlX2FsbCA9IGMoKQogIHRlc3Rfcm1zZV9hbGwgPSBjKCkKICBmb3IodHJhaW5fc2l6ZSBpbiB0cmFpbl9zaXplcyl7CiAgICB0cmFpbl9ybXNlX29uZV9zaXplID0gYygpCiAgICB0ZXN0X3Jtc2Vfb25lX3NpemUgPSBjKCkKICAgIGZvcihyIGluIDE6bnVtX3JlcGVhdCl7CiAgICAgIHNhbXBsZSA8LSBzYW1wbGUuaW50KG4gPSBucm93KHRyYWluX2RmKSwgc2l6ZSA9IGZsb29yKHRyYWluX3NpemUqbnJvdyh0cmFpbl9kZikpLCByZXBsYWNlID0gRikKICAgICAgdHJhaW5fZGZfc3Vic2V0ID0gdHJhaW5fZGZbc2FtcGxlLCBdCiAgICAgIAogICAgICAjIFRyYWluaW5nIGFuZCBldmFsdWF0aW5nIG1vZGVsCiAgICAgIG1vZDEgPSBsbShtb2RlbF9leHAsIGRhdGE9dHJhaW5fZGZfc3Vic2V0KQogICAgICBtb2RlbF9ldmFsID0gZ2V0cm1zZShtb2QxLCB0ZXN0X2RmKQogICAgICB0cmFpbl9ybXNlID0gbW9kZWxfZXZhbCR0cmFpbl9ybXNlCiAgICAgIHRlc3Rfcm1zZSA9IG1vZGVsX2V2YWwkdGVzdF9ybXNlCiAgICAgIAogICAgICB0cmFpbl9ybXNlX29uZV9zaXplID0gYyh0cmFpbl9ybXNlX29uZV9zaXplLCB0cmFpbl9ybXNlKQogICAgICB0ZXN0X3Jtc2Vfb25lX3NpemUgPSBjKHRlc3Rfcm1zZV9vbmVfc2l6ZSwgdGVzdF9ybXNlKQogICAgfQogICAgdHJhaW5fcm1zZV9hbGwgPSBjKHRyYWluX3Jtc2VfYWxsLCBtZWFuKHRyYWluX3Jtc2Vfb25lX3NpemUpKQogICAgdGVzdF9ybXNlX2FsbCA9IGModGVzdF9ybXNlX2FsbCwgbWVhbih0ZXN0X3Jtc2Vfb25lX3NpemUpKQogIH0KICAKICBybXNlX3R5cGUgPSBjKHJlcCgndHJhaW4nLCBsZW5ndGgodHJhaW5fc2l6ZXMpKSwgcmVwKCd0ZXN0JywgbGVuZ3RoKHRyYWluX3NpemVzKSkpCiAgcm1zZXMgPSBjKHRyYWluX3Jtc2VfYWxsLCB0ZXN0X3Jtc2VfYWxsKQogIAogIGRmX2V2YWwgPSBkYXRhLmZyYW1lKHRyYWluX3NpemU9dHJhaW5fc2l6ZXMsIHJtc2VfdHlwZT1ybXNlX3R5cGUsIHJtc2VzPXJtc2VzKQogIGJlc3RfdGVzdF9ybXNlID0gbWluKGRmX2V2YWxbZGZfZXZhbCRybXNlX3R5cGU9PSd0ZXN0JywgJ3Jtc2VzJ10pCiAgYmVzdF90cmFpbl9zaXplID0gdHJhaW5fc2l6ZXNbd2hpY2gubWluKGRmX2V2YWxbZGZfZXZhbCRybXNlX3R5cGU9PSd0ZXN0JywgJ3Jtc2VzJ10pXQoKICByZXR1cm4obGlzdCgnZGZfZXZhbCc9ZGZfZXZhbCwKICAgICAgICAgICAgICAnYmVzdF90ZXN0X3Jtc2UnPWJlc3RfdGVzdF9ybXNlLAogICAgICAgICAgICAgICdiZXN0X3RyYWluX3NpemUnPWJlc3RfdHJhaW5fc2l6ZSkpICAKfQpgYGAKCiMgVGFza3MKCkVhY2ggb2YgdGhlIGZvbGxvd2luZyB0YXNrcyBpcyB3b3J0aCAyMCBwb2ludHMsIGZvciBhIHRvdGFsIG9mIDEwMCBwb2ludHMgZm9yIHRoaXMgcHJvamVjdC4gUmVtZW1iZXIgdG8gYnVpbGQgZWFjaCBtb2RlbCBhcyBzcGVjaWZpZWQsIGV2YWx1YXRlIGl0IHVzaW5nIHRoZSBzdHJhdGVneSBvdXRsaW5lZCBhYm92ZSwgYW5kIHBsb3QgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9ycyBieSB0cmFpbmluZyBzZXQgc2l6ZSAoJSkuCgojIyAxLiBOdW1lcmljIHZhcmlhYmxlcwoKVXNlIExpbmVhciBSZWdyZXNzaW9uIHRvIHByZWRpY3QgYEdyb3NzYCBiYXNlZCBvbiBhdmFpbGFibGUgX251bWVyaWNfIHZhcmlhYmxlcy4gWW91IGNhbiBjaG9vc2UgdG8gaW5jbHVkZSBhbGwgb3IgYSBzdWJzZXQgb2YgdGhlbS4KCmBgYHtyfQojIFRPRE86IEJ1aWxkICYgZXZhbHVhdGUgbW9kZWwgMSAobnVtZXJpYyB2YXJpYWJsZXMgb25seSkKbnVtZXJpY19jb2xzID0gY29sbmFtZXMoZGZbLCBzYXBwbHkoZGYsIGlzLm51bWVyaWMpXSkKcHJpbnQobnVtZXJpY19jb2xzKQpwcmludChzdW1tYXJ5KGRmWywgbnVtZXJpY19jb2xzXSkpCgojIE51bWJlciBvZiB0aW1lcyB0byByZXBlYXQgdHJhaW5pbmcgZm9yIHN0YWJpbGl0eQpudW1fcmVwZWF0ID0gMTAKbW9kZWwxX2NvbHMgPSBjKCdyZWxlYXNlZF95ZWFyJywgJ1J1bnRpbWUnLCAnaW1kYlJhdGluZycsICdpbWRiVm90ZXMnLCAnQnVkZ2V0JykKbW9kZWwxX2V4cCA9IHBhc3RlKCdHcm9zc34nLCBwYXN0ZShtb2RlbDFfY29scywgY29sbGFwc2U9JysnKSkKCmV2YWxfbW9kZWwxID0gZXZhbF9tb2RlbCh0cmFpbl9kZiwgdGVzdF9kZiwgbW9kZWwxX2V4cCwgdHJhaW5fc2l6ZXMsIG51bV9yZXBlYXQ9MTApCmRmX2V2YWwgPSBldmFsX21vZGVsMSRkZl9ldmFsCmBgYAoKKipRKio6IExpc3QgdGhlIG51bWVyaWMgdmFyaWFibGVzIHlvdSB1c2VkLgoKKipBKio6IApUaGUgbnVtZXJpYyB2YXJpYWJsZXMgdGhhdCB3ZSB1c2VkIHdlcmUgYHIge251bWVyaWNfY29sc31gLiAoUmVjYWxsIHRoYXQgd2Ugc3RyaXBwZWQgb3V0IHRoZSB0b21hdG8tbGlrZSBjb2x1bW5zIGluIG91ciBwcmVwcm9jZXNzaW5nIHN0ZXBzIGFib3ZlLikKCioqUSoqOiBXaGF0IGlzIHRoZSBiZXN0IG1lYW4gdGVzdCBSTVNFIHZhbHVlIHlvdSBvYnNlcnZlZCwgYW5kIGF0IHdoYXQgdHJhaW5pbmcgc2V0IHNpemU/CgoqKkEqKjogCgpgYGB7cn0KIyBQbG90dGluZyBUcmFpbmluZyBhbmQgVGVzdCBSTVNFCmdncGxvdChkYXRhPWRmX2V2YWwsIGFlcyh4PXRyYWluX3NpemUsIHk9cm1zZXMpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1ybXNlX3R5cGUsIGdyb3VwPXJtc2VfdHlwZSkpICsKICBnZ3RpdGxlKCdNb2RlbCAxOiBUcmFpbmluZyBhbmQgVGVzdCBSTVNFJykKCiMgQmVzdCB0ZXN0IFJNU0UKYmVzdF90ZXN0cm1zZV9tb2QxID0gZXZhbF9tb2RlbDEkYmVzdF90ZXN0X3Jtc2UKYmVzdF90cmFpbl9zaXplX21vZDEgPSBldmFsX21vZGVsMSRiZXN0X3RyYWluX3NpemUKYGBgCgpUaGUgYmVzdCBtZWFuIHRlc3QgUk1TRSB2YWx1ZSBJIG9ic2VydmVkIHdhcyBgciB7YmVzdF90ZXN0cm1zZV9tb2QxfWAgYW5kIEkgb2JzZXJ2ZWQgdGhhdCBhdCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgYHIge2Jlc3RfdHJhaW5fc2l6ZV9tb2QxICogMTAwfWAgcGVyY2VudC4gTm90aWNlIGZyb20gdGhlIGdyYXBoIGFib3ZlIGFzIHdlbGwgdGhhdCB3ZSBzZWUgdGhhdCBUZXN0IFJNU0UgZ2VuZXJhbGx5IGRlY3JlYXNlcyB3aXRoIGhpZ2hlciB0cmFpbmluZyBzZXQuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aXRoIHZlcnkgbG93IHRyYWluaW5nIHNldCBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBtb3JlIGxpbWl0ZWQgZGF0YS4KCiMjIDIuIEZlYXR1cmUgdHJhbnNmb3JtYXRpb25zCgpUcnkgdG8gaW1wcm92ZSB0aGUgcHJlZGljdGlvbiBxdWFsaXR5IGZyb20gKipUYXNrIDEqKiBhcyBtdWNoIGFzIHBvc3NpYmxlIGJ5IGFkZGluZyBmZWF0dXJlIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMuIEV4cGxvcmUgYm90aCBudW1lcmljIHRyYW5zZm9ybWF0aW9ucyBzdWNoIGFzIHBvd2VyIHRyYW5zZm9ybXMgYW5kIG5vbi1udW1lcmljIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgbGlrZSBiaW5uaW5nIChlLmcuIGBpc19idWRnZXRfZ3JlYXRlcl90aGFuXzNNYCkuCgpgYGB7ciBoZWxwZXItcGxvdC1yZWxhdGl2ZS1wcm9wb3J0aW9uc30KIyBIZWxwZXIgZnVuY3Rpb24gdG8gcGxvdCByZWxhdGl2ZSBwcm9wb3J0aW9ucwpwbG90X3JlbGF0aXZlX3Byb3BvcnRpb25zID0gZnVuY3Rpb24oZGYsIGNvbHMpewogICMgS2V5d29yZCBBcmdzOgogICMgIGRmOiBEYXRhRnJhbWUgd2l0aCBjb2x1bW5zIHlvdSB3YW50IHRvIHBsb3QgcmVsYXRpdmUgcHJvcG9ydGlvbnMgb2YKICAjICBjb2xzOiBUaGUgY29sdW1ucyB5b3Ugd2FudCB0byBwbG90IHJlbGF0aXZlIHByb3BvcnRpb25zIG9mCiAgIyBSZXR1cm5zOgogICMgIHBsb3RfcHJvcG9ydGlvbnM6IGdncGxvdCBvZiBwcm9wb3J0aW9ucwogICMgIHRvcF9jb2xzOiBWZWN0b3Igb2YgQ29sdW1uIENvdW50cwogIGRmLnN1YnNldCA9IGRmW2MoJ1RpdGxlJywgY29scyldCiAgZGYubG9uZyA9IG1lbHQoZGYuc3Vic2V0LCBpZC52YXJzPWMoJ1RpdGxlJykpCiAgCiAgIyBSZW1vdmluZyByb3dzIHdoZXJlIHZhbHVlIGlzIDAgYmVjYXVzZSB3aGVuIHZhbHVlIGlzIDAsIHRoYXQgbWVhbnMgbW92aWUgaXMgbm90IHRoYXQgZ2VucmUuCiAgZGYubG9uZyA9IGRmLmxvbmdbYXBwbHkoZGYubG9uZ1sndmFsdWUnXSwgMSwgZnVuY3Rpb24oeikgIWFueSh6PT0wKSksIF0KCiAgIyBGaW5hbGx5IHBsb3QgcmVsYXRpdmUgcHJvcG9ydGlvbnMKICAjIGh0dHBzOi8vc2ViYXN0aWFuc2F1ZXIuZ2l0aHViLmlvL3BlcmNlbnRhZ2VfcGxvdF9nZ3Bsb3QyX1YyLwogIHBsb3RfcHJvcG9ydGlvbnMgPSBnZ3Bsb3QoZGYubG9uZywgYWVzKHg9dmFyaWFibGUpKSArCiAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogICAgeWxhYignUmVsYXRpdmUgUHJvcG9ydGlvbicpCiAgCiAgdG9wX2NvbHMgPSBzb3J0KGNvbFN1bXMoZGZbY29sc10pLCBkZWNyZWFzaW5nPVRSVUUpCiAgcmV0dXJuKGxpc3QoJ3Bsb3RfcHJvcG9ydGlvbnMnPXBsb3RfcHJvcG9ydGlvbnMsCiAgICAgICAgICAgICAgJ3RvcF9jb2xzJz10b3BfY29scykpICAKfQpgYGAKCmBgYHtyIGhlbHBlci10cmFuc2Zvcm1zfQojIEZyb20gTGVzc29uIDg6IFByZXByb2Nlc3NpbmcgRGF0YSAoMjIuIFNrZXduZXNzIGFuZCBQb3dlciBUcmFuc2Zvcm1hdGlvbnMpCiMgQWxzbyBmcm9tIExlc3NvbmcgMTA6IExvZ2lzdGljIFJlZ3Jlc3Npb24gKDE5LiBJbmNyZWFzaW5nIERhdGEgRGltZW5zaW9uYWxpdHkpCiMgVGhlIFRyYW5zZm9ybXMgd2UgYXJlIGdvaW5nIHRvIHVzZSBhcmUKIyAxLiB4XjIKIyAyLiBsb2coeCkgKGluIHRoZW9yeSB3ZSBjb3VsZCBjaG9vc2UgZGlmZmVyZW5jZSB2YWx1ZXMgb2YgbGFtYmRhIHRvIGNob29zZSBkaWZmZXJlbnQgdHlwZXMgb2YgcG93ZXIgdHJhbnNmb3JtYXRpb25zLiBJbiB0aGlzIGNhc2UgZm9yIHNpbXBsaWNpdHksIHdlIHdpbGwgc3RpY2sgd2l0aCBsb2cgdHJhbnNmb3JtYXRpb25zIChlLmcuIGxhbWJkYSA9IDApKQoKIyBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIHBvd2VyIHRyYW5zZm9ybXMKY3JlYXRlX3Bvd2VyX3RyYW5zZm9ybXMgPSBmdW5jdGlvbihkZiwgY29sdW1uKXsKICAjIEtleXdvcmQgQXJnczoKICAjICBkZjogRGF0YUZyYW1lIHdpdGggZGF0YSB0aGF0IHlvdSB3YW50IHRvIGFkZCBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgdG8KICAjICBjb2x1bW46IENvbHVtbiB3aXRoaW4gZGYgdGhhdCB5b3Ugd2FudCB0byBkbyBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgdG8KICAjIFJldHVybnM6CiAgIyAgZGY6IERhdGFGcmFtZSB3aXRoIG5ldyBjb2x1bW5zIGFzIHRyYW5zZm9ybWF0aW9ucwogIAogICMgUG93ZXIgVHJhbnNmb3JtcwogICMgRm9yIG5vdywgbGV0J3Mgb25seSBjb25zaWRlciBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgd2hlcmUgbGFtYmRhID0gMiBvciAtMS4gSW4gdGhlb3J5CiAgIyB3ZSBjb3VsZCB0cnkgaW5maW5pdGUgdmFsdWVzIG9mIGxhbWJkYSB3aXRoIHNvbWUga2luZCBvZiBjcm9zcyB2YWxpZGF0aW9uIGFwcHJvYWNoIHRvIHR1bmUgdGhpcwogICMgYnV0IGZvciBzaW1wbGljaXR5IGxldCdzIGp1c3QgdXNlIGludmVyc2UgYW5kIHF1YWRyYXRpYy4KICBkZltbcGFzdGUwKGNvbHVtbiwgJzInKV1dID0gKGRmW1tjb2x1bW5dXSBeIDIgLSAxKSAvIDIgICMgbGFtYmRhID0gMgogIGRmW1twYXN0ZTAoY29sdW1uLCAnbmVnMScpXV0gPSAtMSAqIChkZltbY29sdW1uXV0gXiAoLTEpIC0gMSkgLyAoLTEpICAjIGxhbWJkYSA9IC0xCiAgZGZbW3Bhc3RlMCgnbG9nJywgY29sdW1uKV1dID0gbG9nKGRmW1tjb2x1bW5dXSkKICByZXR1cm4oZGYpCn0KCmNyZWF0ZV9iaW5uaW5nX3RyYW5zZm9ybXMgPSBmdW5jdGlvbih4LCB0aHJlc2hvbGQpewogICMgS2V5d29yZCBBcmdzOgogICMgIHg6IFZlY3RvciBvZiBkYXRhIHlvdSB3YW50IHRvIHRyYW5zZm9yCiAgIyAgdGhyZXNob2xkOiBUaHJlc2hvbGQgb3ZlciB3aGljaCB0cmFuc2Zvcm1lZCB2ZWN0b3IgZXF1YWxzIDEsIGVsc2UgMAogICMgUmV0dXJuczoKICAjICBiaW5uZWQ6IE5ldyBiaW5uZWQgdHJhbnNmb3JtZWQgdmVjdG9yCiAgCiAgIyBQb3dlciBUcmFuc2Zvcm1zCiAgYmlubmVkID0gYXMubnVtZXJpYyh4ID49IHRocmVzaG9sZCkKICByZXR1cm4oYmlubmVkKQp9CmBgYAoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiMgVE9ETzogQnVpbGQgJiBldmFsdWF0ZSBtb2RlbCAyICh0cmFuc2Zvcm1lZCBudW1lcmljIHZhcmlhYmxlcyBvbmx5KQoKIyBDcmVhdGluZyBuZXcgdmFyaWFibGVzCmRmJGlzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtID0gYXMubnVtZXJpYyhkZiRCdWRnZXQgPj0gNjAwMDAwMDApCmRmJGltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1ID0gYXMubnVtZXJpYyhkZiRpbWRiUmF0aW5nID49IDcuNSkKZGYgPSBjcmVhdGVfcG93ZXJfdHJhbnNmb3JtcyhkZiwgJ0J1ZGdldCcpCmRmID0gY3JlYXRlX3Bvd2VyX3RyYW5zZm9ybXMoZGYsICdSdW50aW1lJykKZGYgPSBjcmVhdGVfcG93ZXJfdHJhbnNmb3JtcyhkZiwgJ2ltZGJSYXRpbmcnKQpkZiA9IGNyZWF0ZV9wb3dlcl90cmFuc2Zvcm1zKGRmLCAnaW1kYlZvdGVzJykKZGYgPSBjcmVhdGVfcG93ZXJfdHJhbnNmb3JtcyhkZiwgJ3JlbGVhc2VkX3llYXInKQoKIyBBZGRpbmcgYmlucyBieSB5ZWFycwpkZiRlYXJseV8yMDAwcyA9IGFzLm51bWVyaWMoZGYkcmVsZWFzZWRfeWVhciA8PSAyMDA0KQpkZiRtaWRfMjAwMHMgPSBhcy5udW1lcmljKGRmJHJlbGVhc2VkX3llYXIgPj0gMjAwNSAmIGRmJHJlbGVhc2VkX3llYXIgPD0gMjAwOSkKZGYkcG9zdF8yMDEwID0gYXMubnVtZXJpYyhkZiRyZWxlYXNlZF95ZWFyID49IDIwMTApCgojIEFkZGluZyBiaW5zIGJ5IFJ1bnRpbWUKZGYkUnVudGltZVVuZGVyNzUgPSBhcy5udW1lcmljKGRmJFJ1bnRpbWUgPD0gNzUpCmRmJFJ1bnRpbWVCZXR3ZWVuNzVfMTI1ID0gYXMubnVtZXJpYyhkZiRSdW50aW1lID4gNzUgJiBkZiRSdW50aW1lIDw9IDEyNSkKZGYkUnVudGltZUdyZWF0ZXIxMjUgPSBhcy5udW1lcmljKGRmJFJ1bnRpbWUgPiAxMjUpCgojICJSZWNyZWF0ZSIgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRmIHdpdGggdGhlIHNhbWUgaW5kaWNlcyB0byBpbmNsdWRlIHRoZSBuZXcgY29sdW1ucwp0cmFpbl9kZiA8LSBkZlt0cmFpbl9pZHgsIF0KdGVzdF9kZiAgPC0gZGZbLXRyYWluX2lkeCwgXQoKbW9kZWwyX2NvbHMgPSBjKG1vZGVsMV9jb2xzLCBjKCdpbWRiUmF0aW5nX2dyZWF0ZXJfdGhhbl83NScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnaW1kYlJhdGluZzInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2lzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdSdW50aW1lVW5kZXI3NScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUnVudGltZUJldHdlZW43NV8xMjUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1J1bnRpbWVHcmVhdGVyMTI1JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdSdW50aW1lMicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZWFybHlfMjAwMHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21pZF8yMDAwcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncG9zdF8yMDEwJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKbW9kZWwyX2V4cCA9IHBhc3RlKCdHcm9zc34nLCBwYXN0ZShtb2RlbDJfY29scywgY29sbGFwc2U9JysnKSkKCmV2YWxfbW9kZWwyID0gZXZhbF9tb2RlbCh0cmFpbl9kZiwgdGVzdF9kZiwgbW9kZWwyX2V4cCwgdHJhaW5fc2l6ZXMsIG51bV9yZXBlYXQ9MTApCmRmX2V2YWxfbW9kMiA9IGV2YWxfbW9kZWwyJGRmX2V2YWwKYGBgCgoqKlEqKjogRXhwbGFpbiB3aGljaCB0cmFuc2Zvcm1hdGlvbnMgeW91IHVzZWQgYW5kIHdoeSB5b3UgY2hvc2UgdGhlbS4KCioqQSoqOgpJbiBhZGRpdGlvbiB0byB0aGUgZmVhdHVyZXMgZnJvbSBQYXJ0IDEsIEkgY3JlYXRlZCB0aGUgZm9sbG93aW5nIG5ldyBmZWF0dXJlcwoKMS4gQmlubmVkIGltZGJSYXRpbmc6IENyZWF0ZWQgYW4gaW5kaWNhdG9yIHZhcmlhYmxlIGZvciBpZiB0aGUgaW1kYlJhdGluZyB3YXMgZ3JlYXRlciB0aGFuIDcuNQoyLiBQb3dlciBUcmFuc2Zvcm1hdGlvbiBmb3IgaW1kYlJhdGluZzogbGFtYmRhPTIKMy4gQmlubmVkIEJ1ZGdldDogQ3JlYXRlZCBhbiBpbmRpY2F0b3IgdmFyaWFibGUgZm9yIGlmIGJ1ZGdldCBncmVhdGVyIHRoYW4gNjAgbWlsbGlvbgo0LiBCaW5uZWQgUnVudGltZSAodW5kZXIgNzUsIGJldHdlZW4gNzUgYW5kIDEyNSwgYW5kIG92ZXIgMTI1KQo1LiBQb3dlciBUcmFuc2Zvcm1hdGlvbiBmb3IgUnVudGltZTogbGFtYmRhPTIKNi4gQmlubmVkIFJlbGVhc2VkIFllYXIgKGJldHdlZW4gMjAwMCBhbmQgMjAwNDsgMjAwNSBhbmQgMjAwOTsgMjAxMCBhbmQgYWZ0ZXIpCgpGaXJzdCwgSSBjcmVhdGVkIGEgYmlubmVkIHZhcmlhYmxlIGZvciBpbWRiUmF0aW5nIGF0IDcuNS4gKE5vdGUsIGxhdGVyIG9uIGluIHBhcnQgNSBhcyB3ZSBleHBsb3JlIG1vcmUgaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwcywgSSBpbnRlcmFjdCB0aGlzIGJpbm5lZCB2YXJpYWJsZSB3aXRoIGltZGJSYXRpbmcgdG8gY2FwdHVyZSBhIGRpZmZlcmVudCByZWxhdGlvbnNoaXAgYWZ0ZXIgNy41IC0tIG1vcmUgb24gdGhpcyBiZWxvdykuCgpJIGNob3NlIHRvIGxvb2sgYXQgaW1kYlJhdGluZyBiZWNhdXNlIEkgbm90aWNlZCBhbiBpbnRlcmVzdGluZyByZWxhdGlvbnNoaXAgd2l0aCBpbWRiUmF0aW5nIGFuZCBHcm9zcy4gSWYgeW91IGxvb2sgYXQgdGhlIHBsb3QgYmVsb3csIHdlIHBsb3QgdGhlIGF2ZXJhZ2UgR3Jvc3MgZ2l2ZW4gaW1kYlJhdGluZywgYW5kIG5vdGljZSB0aGF0IHRoZSByZWxhdGlvbnNoaXAgc2VlbXMgdG8gYmUgbGluZWFyIHVudGlsIGFib3V0IDcuNS4gVGhlbiB0aGUgYXZlcmFnZSBncm9zcyBpbmNyZWFzZXMgYSBsb3QuIFRoaXMgaXMgYW4gaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwIGJlY2F1c2UgbWF5YmUgbW92aWVzIGJlbG93IDcuNSByYXRpbmcgYXJlIG1vc3RseSBub3QgdGhhdCBncmVhdCBhbmQgc28gdGhlIHJlbGF0aW9uc2hpcCBpcyBsaW5lYXIuIEJ1dCBpZiBhIG1vdmllIGlzIHJlYWxseSByZWFsbHkgZ29vZCwgd2hlbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gR3Jvc3MgYW5kIHJhdGluZyBjaGFuZ2VzLCB0aGF0IGlzIGEgcmVhbGx5IHJlYWxseSBnb29kIG1vdmllIGdyb3NzZXMgbXVjaCBtdWNoIG1vcmUuIFRodXMsIEkgY2hvc2UgdG8gY3JlYXRlIGEgYmlubmVkIHZhcmlhYmxlIGF0IGBpbWRiUmF0aW5nID0gNy41YC4gVGhpcyB3aWxsIGFsbG93IGZvciB0aGUgbW9kZWwgdG8gcG90ZW50aWFsbHkgY2FwdHVyZSBhIGRpZmZlcmVuY2UgaW4gdGhlIGVmZmVjdCBvZiByYXRpbmcgb24gYEdyb3NzYCBmb3IgbW92aWVzIHdpdGggcmF0aW5ncyBncmVhdGVyIHRoYW4gNy41LiBIb3dldmVyLCBmcm9tIHRoZSBncmFwaCwgbm90aWNlIHRoYXQgdGhlIHNsb3BlIG9mIHRoZSByZWxhdGlvbnNoaXAgaXMgbm90IGNvbnN0YW50IGJlZm9yZSBhbmQgYWZ0ZXIgNy41LiBUaHVzLCBpbiBwYXJ0IDUsIHdlIHdpbGwgYWxzbyBjcmVhdGUgYW4gaW50ZXJhY3Rpb24gdmFyaWFibGUgdG8gdHJ5IHRvIGNhcHR1cmUgYSBkaWZmZXJlbnQgcmVsYXRpb25zaGlwIGFmdGVyIGBpbWRiUmF0aW5nID0gNy41YCB3aXRoIGBpbWRiUmF0aW5nMmAgd2hlcmUgYGltZGJSYXRpbmcyYCBpcyB0aGUgc3F1YXJlZCB0ZXJtIG9mIGltZGJSYXRpbmcgKHNpbmNlIHRoZSByZWxhdGlvbnNoaXAgYWZ0ZXIgaW1kYlJhdGluZz03LjUgbG9va3MgZXhwb25lbnRpYWwuIEZ1cnRoZXJtb3JlLCBsb29raW5nIGF0IHRoZSBgR0dQYWlyc2AgZ3JhcGgsIGl0IGxvb2tzIGxpa2UgYGltZGJSYXRpbmcyYCBoYXMgYSBoaWdoZXIgbGluZWFyIGNvcnJlbGF0aW9uIHdpdGggYEdyb3NzYCB0aGFuIGBpbWRiUmF0aW5nYC4gVGhlIG90aGVyIHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBkaWQgbm90IHNlZW0gdG8gaW5jcmVhc2UgbGluZWFyIGNvcnJlbGF0aW9uLikKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkZiwgYWVzKGltZGJSYXRpbmcsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikgKwogIGdndGl0bGUoJ1JlbGF0aW9uc2hpcCBiZXR3ZWVuIEdyb3NzIGFuZCBpbWRiUmF0aW5nJykKCmdncGFpcnMoZGZbYygnaW1kYlJhdGluZycsICdpbWRiUmF0aW5nMicsICdpbWRiUmF0aW5nbmVnMScsICdsb2dpbWRiUmF0aW5nJywgJ0dyb3NzJyldKQpgYGAKCk5leHQsIEkgbG9va2VkIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgR3Jvc3NgIGFuZCBgaW1kYlZvdGVzYC4gRnJvbSB0aGUgZ3JhcGhzIGJlbG93LCBpdCBkb2Vzbid0IGxvb2sgbGlrZSB0aGVyZSBhcmUgbWVhbmluZ2Z1bCBiaW5zIHRoYXQgd291bGQgYWRkIHZhbHVlLiBGdXJ0aGVybW9yZSwgbG9va2luZyBhdCB0aGUgYEdHUGFpcnNgIHBsb3QsIG5vbmUgb2YgdGhlIHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBhZGQgaGlnaGVyIGxpbmVhciBjb3JyZWxhdGlvbiB3aXRoIEdyb3NzLCBzbyBpdCBkb2VzIG5vdCBtYWtlIHNlbnNlIHRvIGFkZCBhZGRpdGlvbmFsIHRyYW5zZm9ybWF0aW9ucyB3aXRoIGBpbWRiVm90ZXNgIGludG8gb3VyIG1vZGVsLgoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2dwbG90KGRmLCBhZXMoaW1kYlZvdGVzLCBHcm9zcykpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMiwgZ2VvbSA9ICJwb2ludCIpICsKICBnZ3RpdGxlKCdSZWxhdGlvbnNoaXAgYmV0d2VlbiBHcm9zcyBhbmQgaW1kYlZvdGVzJykKCmdncGFpcnMoZGZbYygnaW1kYlZvdGVzJywgJ2ltZGJWb3RlczInLCAnaW1kYlZvdGVzbmVnMScsICdsb2dpbWRiVm90ZXMnLCAnR3Jvc3MnKV0pCmBgYAoKTmV4dCwgSSBsb29rIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBCdWRnZXQgYW5kIEdyb3NzLiBUaGlzIGlzIGJlY2F1c2UgaWYgd2UgcmVtZW1iZXIgYmFjayB0byBQcm9qZWN0IDEsIGhpZ2hlciBidWRnZXQgZmlsbXMgdGVuZGVkIHRvIGdyb3NzIG11Y2ggaGlnaGVyLiBMb29raW5nIGF0IHRoZSBncmFwaHMgYmVsb3csIGl0IHNlZW1zIGxpa2UgJDYwTSB3b3VsZCBiZSBhbiBpbnRlcmVzdGluZyBiaW4uIEhvd2V2ZXIsIHdlIGFsc28gcGxvdCB0aGUgR0dQYWlycyBwbG90IG9mIGBCdWRnZXQyYCAocG93ZXIgdHJhbnNmb3JtYXRpb24gd2l0aCBsYW1iZGEgPSAyKSwgYEJ1ZGdldG5lZzFgIChwb3dlciB0cmFuc2Zvcm1hdGlvbiB3aXRoIGxhbWJkYSA9IC0xKSwgYGxvZ0J1ZGdldGAgKHBvd2VyIHRyYW5zZm9ybWF0aW9uIHdpdGggbGFtYmRhID0gMCkgYW5kIGBCdWRnZXRgIGFnYWluc3QgYEdyb3NzYCBhbmQgeW91IGNhbiBzZWUgYXQgbGVhc3Qgd2l0aCB0aGUgbGluZWFyIGNvcnJlbGF0aW9uLCBub25lIG9mIHRoZSBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgb2ZmZXIgYmV0dGVyIGxpbmVhciBjb3JyZWxhdGlvbiB3aXRoIGBHcm9zc2AgdGhhbiBgQnVkZ2V0YC4gVGh1cywgd2UgY2hvb3NlIG5vdCB0byBhZGQgYW55IG9mIHRoZSBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgb2YgYEJ1ZGdldGAgaW50byB0aGlzIG1vZGVsLgoKYGBge3J9CiMgRGlzdHJpYnV0aW9uIG9mIEdyb3NzIGJ5IEJ1ZGdldAojIE5vdyB3ZSBuZWVkIHRvIHNob3cgZGlzdHJpYnV0aW9uIG9mIHJ1bnRpbWUgYnkgQnVkZ2V0LiBEbyBhIGJveHBsb3QgZ3JvdXBlZCBieSBidWRnZXQKZGYkYnVkZ2V0X3JvdW5kZWQgPSByb3VuZF9hbnkoZGYkQnVkZ2V0LCAxMDAwMDAwMCwgZiA9IGZsb29yKQpkZiRidWRnZXRfcm91bmRlZFtkZiRCdWRnZXQgPj0gNjAwMDAwMDBdID0gNjAwMDAwMDAKZGYkYnVkZ2V0X3JvdW5kZWQgPSBhcy5jaGFyYWN0ZXIoZGYkYnVkZ2V0X3JvdW5kZWQpCmRmJGJ1ZGdldF9yb3VuZGVkW2RmJGJ1ZGdldF9yb3VuZGVkID09ICc2MDAwMDAwMDAnXSA9ICdPdmVyIDYwTScKCmdncGxvdChkZiwgYWVzKGFzLmZhY3RvcihidWRnZXRfcm91bmRlZCksIEdyb3NzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3hfZGlzY3JldGUoIkJ1ZGdldCBSb3VuZGVkIikgKwogIGdndGl0bGUoJ0Rpc3RyaWJ1dGlvbiBvZiBHcm9zcyBieSBCdWRnZXQnKQoKZ2dwbG90KGRmLCBhZXMoQnVkZ2V0LCBHcm9zcykpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMiwgZ2VvbSA9ICJwb2ludCIpCgpnZ3BhaXJzKGRmW2MoJ0J1ZGdldCcsICdCdWRnZXQyJywgJ0J1ZGdldG5lZzEnLCAnbG9nQnVkZ2V0JywgJ0dyb3NzJyldKQpgYGAKCk5leHQsIHdlIGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIFJ1bnRpbWUgYW5kIEdyb3NzLiBUaGVyZSBhcmUgYSBmZXcgdGhpbmdzIHRoYXQgYXJlIGludGVyZXN0aW5nIGhlcmUuIE5vdGljZSBmaXJzdCB0aGF0IGlmIHdlIGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBHcm9zc2AgYW5kIGBSdW50aW1lYCB0aGVyZSBzZWVtIHRvIGJlIGEgZmV3IGludGVyZXN0aW5nICJjdXQgcG9pbnRzIi4gRm9yIGBSdW50aW1lYCBiZXR3ZWVuIDAgYW5kIDc1ICh1cCB1bnRpbCB0aGUgb3JhbmdlIGRvdHRlZCBsaW5lKSwgdGhlIHJlbGF0aW9uc2hpcCBzZWVtcyB0byBiZSBmbGF0LiBGb3IgYFJ1bnRpbWVgIGJldHdlZW4gNzUgYW5kIDEyNSwgdGhlIHJlbGF0aW9uc2hpcCBpcyBsaW5lYXJseSBwb3NpdGl2ZWx5IHNsb3BlZC4gRmluYWxseSwgZm9yIGBSdW50aW1lYCBtb3JlIHRoYW4gMTI1IChvdmVyIDIgaG91cnMpLCB0aGUgcmVsYXRpb25zaGlwIHdpdGggR3Jvc3Mgc2VlbXMgdG8gYmUgdmVyeSBoaWdobHkgdmFyaWFibGUuIEZ1cnRoZXJtb3JlLCB0aGUgb3ZlcmFsbCByZWxhdGlvbnNoaXAgc2VlbXMgdG8gYmUgc2xpZ2h0bHkgcXVhZHJhdGljOyBub3RpY2UgdGhhdCBpbiB0aGUgYEdHUGFpcnNgIHBsb3QsIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGBSdW50aW1lMmAgKHBvd2VyIHRyYW5zZm9ybWF0aW9uIHdpdGggbGFtYmRhID0gMikgYW5kIGBHcm9zc2AgIGlzIGhpZ2hlciB0aGFuIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGBSdW50aW1lYCBhbmQgYEdyb3NzYC4gVGh1cywgd2UgY2hvb3NlIHRvIGFkZCBiaW5zIGZvciBgUnVudGltZWAgdXAgdG8gNzUsIGJldHdlZW4gNzUgYW5kIDEyNSwgYW5kIG92ZXIgMTI1LiBGdXJ0aGVybW9yZSwgd2UgY2hvb3NlIHRvIGFkZCBwb3dlciB0cmFuc2Zvcm1hdGlvbiBvZiBgUnVudGltZTJgLiBJbiBRdWVzdGlvbiA1LCB3ZSB3aWxsIGFsc28gZXhwbG9yZSBhZGRpbmcgaW50ZXJhY3Rpb25zIGJldHdlZW4gdGhlIGJpbm5lZCB2YXJpYWJsZSBhbmQgdGhlIHBvd2VyIHRyYW5zZm9ybWF0aW9uLCB3aGljaCBzZWVtcyBhcHByb3ByaWF0ZSBzaW5jZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYFJ1bnRpbWVgIGFuZCBgR3Jvc3NgIGRvZXMgbm90IHNlZW0gdG8gYmUgdGhlIHNhbWUgd2l0aGluIHRoZXNlIHBvaW50cy4KCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkZiwgYWVzKFJ1bnRpbWUsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD03NSwgY29sb3I9J29yYW5nZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MTI1LCBjb2xvcj0nYmx1ZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZ3RpdGxlKCdBdmVyYWdlIEdyb3NzIHZzIFJ1bnRpbWUnKQoKZ2dwYWlycyhkZltjKCdSdW50aW1lJywgJ1J1bnRpbWUyJywgJ1J1bnRpbWVuZWcxJywgJ2xvZ1J1bnRpbWUnLCAnR3Jvc3MnKV0pCmBgYAoKRmluYWxseSwgdGhlIGxhc3QgbnVtZXJpY2FsIGNvbHVtbiBpcyBgcmVsZWFzZWRfeWVhcmAuIElmIHdlIHBsb3QgdGhlIGF2ZXJhZ2UgZ3Jvc3MgYW1vdW50IGJ5IGByZWxlYXNlZF95ZWFyYCB3ZSBzZWUgdGhlcmUgYXJlIGFjdHVhbGx5ICJjeWNsZXMiIHRoYXQgYXBwZWFyIHRvIGJlIGZvcm1pbmcuCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3QoZGYsIGFlcyhyZWxlYXNlZF95ZWFyLCBHcm9zcykpICsKICBnZW9tX3BvaW50KHN0YXQ9J3N1bW1hcnknKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTIwMDQuNSwgY29sb3I9J29yYW5nZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MjAwOS41LCBjb2xvcj0nYmx1ZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZ3RpdGxlKCdSZWxhdGlvbnNoaXAgYmV0d2VlbiBHcm9zcyBhbmQgUmVsZWFzZWQgWWVhcicpCgpnZ3BhaXJzKGRmW2MoJ3JlbGVhc2VkX3llYXInLCAncmVsZWFzZWRfeWVhcjInLCAncmVsZWFzZWRfeWVhcm5lZzEnLCAnbG9ncmVsZWFzZWRfeWVhcicsICdHcm9zcycpXSkKYGBgCgpGb3IgZXhhbXBsZSwgaW4gdGhlIGdyYXBoIGFib3ZlLCB3ZSBzZWUgdGhhdCBncm9zcyBhbW91bnRzIGFyZSBnZW5lcmFsbHkgaW5jcmVhc2luZyBiZXR3ZWVuIDIwMDAgdG8gMjAwNC4gVGhlbiBkcm9wcyBpbiAyMDA1IGFuZCBnZW5lcmFsbHkgcmlzZXMgYWdhaW4gYmV0d2VlbiAyMDA1IGFuZCAyMDA5LiBUaGVuLCBhdmVyYWdlIGdyb3NzIGFtb3VudHMgZHJvcHMgYWdhaW4gaW4gMjAxMC4gVGhpcyBpcyBsaWtlbHkgcmVmbGVjdGl2ZSBvZiB0aGUgZWNvbm9teS9idXNpbmVzcyBjeWNsZXMuIEVzcGVjaWFsbHkgdGhlIGRyb3AgYmV0d2VlbiAyMDA5IHRvIDIwMTAgY291bGQgYmUgYSByZWZsZWN0aW9uIG9mIHRoZSBHcmVhdCBSZWNlc3Npb24gd2hlcmUgcGVvcGxlIHNwZW50IGxlc3MgbW9uZXkgb24gZW50ZXJ0YWlubWVudC4gVGh1cywgSSB3YW50ZWQgdG8sIGluc3RlYWQgb2YganVzdCB1c2luZyBgcmVsZWFzZWRfeWVhcmAgYXMgYSBjb250aW51b3VzIHZhcmlhYmxlLCBjcmVhdGUgdGhyZWUgYmlucyBvZiB5ZWFyczogZWFybHkgMjAwMCdzLCBtaWQgMjAwMCdzLCBhbmQgcG9zdCAyMDEwLgoKSG93ZXZlciwgbm90aWNlIHRoYXQgaW4gdGhlIGBHR1BhaXJzYCBwbG90LCBub25lIG9mIHRoZSBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgc2VlbWVkIHRvIG9mZmVyIGFueSBhZGRpdGlvbmFsIGxpbmVhciBjb3JyZWxhdGlvbjsgdGh1cyBJIGNob3NlIG5vdCB0byBhZGQgYW55IHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBvZiBgcmVsZWFzZWRfeWVhcmAgaW50byB0aGUgbW9kZWwuCgoqKlEqKjogSG93IGRpZCB0aGUgUk1TRSBjaGFuZ2UgY29tcGFyZWQgdG8gVGFzayAxPwoKKipBKio6IAoKQmVsb3csIHdlIHBsb3QgdGhlIFRyYWluaW5nIGFuZCBUZXN0IFJNU0UgZm9yIE1vZGVsIDIuIEZ1cnRoZXJtb3JlLCB3ZSBjYW4gZGlyZWN0bHkgY29tcGFyZSB0aGUgYmVzdCB0ZXN0IFJNU0UgYmVsb3c6CgpgYGB7cn0KZ2dwbG90KGRhdGE9ZGZfZXZhbF9tb2QyLCBhZXMoeD10cmFpbl9zaXplLCB5PXJtc2VzKSkgKwogIGdlb21fbGluZShhZXMoY29sb3I9cm1zZV90eXBlLCBncm91cD1ybXNlX3R5cGUpKSArCiAgZ2d0aXRsZSgnTW9kZWwgMjogVHJhaW5pbmcgYW5kIFRlc3QgUk1TRScpCgpwcmludChldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UpCgppbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsMiA9IGV2YWxfbW9kZWwyJGJlc3RfdGVzdF9ybXNlIC0gZXZhbF9tb2RlbDEkYmVzdF90ZXN0X3Jtc2UKcGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsMiA9IDEuMCAqIGFicyhldmFsX21vZGVsMiRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlKSAvIGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlCnByaW50KCdJbXByb3ZlbWVudCBpbiBSTVNFJykKcHJpbnQoaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDIpCgojIEFzIGEgcGVyY2VudGFnZQpwcmludChwZXJjZW50YWdlX2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWwyKQpgYGAKCk5vdGljZSB0aGF0IHRoZSBpbXByb3ZlbWVudCBpbiBtb2RlbDIgaXMgYHIge2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWwyfWAgd2hpY2ggYWN0dWFsbHkgcmVwcmVzZW50cyBhIGByIHtwZXJjZW50YWdlX2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWwyICogMTAwfWAgcGVyY2VudGFnZSBnYWluISBXaGlsZSB0aGlzIG1pZ2h0IHNlZW0gbW9kZXN0OyByZW1lbWJlciB0aGF0IHdlIGRpZCBub3RlIHNvbWUgbWVhbmluZ2Z1bCBpbnRlcmFjdGlvbnMgdGhhdCB3aWxsIGJlIGZ1cnRoZXIgZXhwbG9yZWQgaW4gUGFydCA1IQoKQWxzbywgd2UgY2FuIGxvb2sgYXQgd2hlcmUgdGhlIGJlc3QgdGVzdCBNU0Ugb2NjdXJlZCwgYXMgYSBmdW5jdGlvbiBvZiB0cmFpbmluZyBzaXplOgoKYGBge3J9CiMgQmVzdCB0ZXN0IFJNU0UKYmVzdF90ZXN0cm1zZV9tb2QyID0gZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UKYmVzdF90cmFpbl9zaXplX21vZDIgPSBldmFsX21vZGVsMiRiZXN0X3RyYWluX3NpemUKYGBgCgpUaGUgYmVzdCBtZWFuIHRlc3QgUk1TRSB2YWx1ZSBJIG9ic2VydmVkIHdhcyBgciB7YmVzdF90ZXN0cm1zZV9tb2QyfWAgYW5kIEkgb2JzZXJ2ZWQgdGhhdCBhdCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgYHIge2Jlc3RfdHJhaW5fc2l6ZV9tb2QyICogMTAwfWAgcGVyY2VudC4gTm90aWNlIGZyb20gdGhlIGdyYXBoIGFib3ZlIGFzIHdlbGwgdGhhdCB3ZSBzZWUgdGhhdCBUZXN0IFJNU0UgZ2VuZXJhbGx5IGRlY3JlYXNlcyB3aXRoIGhpZ2hlciB0cmFpbmluZyBzZXQuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aXRoIHZlcnkgbG93IHRyYWluaW5nIHNldCBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBtb3JlIGxpbWl0ZWQgZGF0YS4KCiMjIDMuIE5vbi1udW1lcmljIHZhcmlhYmxlcwoKV3JpdGUgY29kZSB0aGF0IGNvbnZlcnRzIGdlbnJlLCBhY3RvcnMsIGRpcmVjdG9ycywgYW5kIG90aGVyIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0byBjb2x1bW5zIHRoYXQgY2FuIGJlIHVzZWQgZm9yIHJlZ3Jlc3Npb24gKGUuZy4gYmluYXJ5IGNvbHVtbnMgYXMgeW91IGRpZCBpbiBQcm9qZWN0IDEpLiBBbHNvIHByb2Nlc3MgdmFyaWFibGVzIHN1Y2ggYXMgYXdhcmRzIGludG8gbW9yZSB1c2VmdWwgY29sdW1ucyAoYWdhaW4sIGxpa2UgeW91IGRpZCBpbiBQcm9qZWN0IDEpLiBOb3cgdXNlIHRoZXNlIGNvbnZlcnRlZCBjb2x1bW5zIG9ubHkgdG8gYnVpbGQgeW91ciBuZXh0IG1vZGVsLgoKYGBge3IgaGVscGVyLW9uZS1ob3QtZW5jb2Rpbmd9CiMgQ3JlYXRlIGEgaGVscGVyIGZ1bmN0aW9uIHRvIGhlbHAgb25lLWhvdCBlbmNvZGUgY2VydGFpbiB0ZXh0IGNvbHVtbnMKIyBGb3IgZXhhbXBsZSwgR2VucmUsIEFjdG9ycywgRGlyZWN0b3JzCiMgQnkgZGVmYXVsdCB3ZSB3aWxsIGtlZXAgdGhlIHRvcCB0ZW4gb2YgZWFjaApjcmVhdGVfb25lX2hvdF9lbmNvZGVkID0gZnVuY3Rpb24oZGYsIGNvbHVtbiwgbnVtVG9wQ29scz0xMCl7CiAgIyBLZXl3b3JkIEFyZ3M6CiAgIyAgZGY6IERhdGFGcmFtZSB3aXRoIGRhdGEgYW5kIGNvbHVtbiB5b3Ugd2FudCB0byBvbmUtaG90LWVuY29kZQogICMgIGNvbHVtbjogQ29sdW1uIHdpdGhpbiB0aGUgZGYgdGhhdCB5b3Ugd2FudCB0byBvbmUtaG90IGVuY29kZQogICMgUmV0dXJuczoKICAjICBkZjogT3JpZ2luYWwgRGF0YUZyYW1lIHdpdGggbmV3bHkgYXBwZW5kZWQgY29sdW1ucyBvZiBudW1Ub3BDb2xzIGhpZ2hlc3Qgb2NjdXJpbmcgdmFsdWUgb2YgQ29sdW1uCiAgIyAgdG9wX2NvbHM6IFRoZSB0b3BfY29scyB0aGF0IHdlcmUgcmV0dXJuZWQKICAKICAjIEV4YW1wbGUgb2YgaG93IHRvIG9uZS1ob3QgZW5jb2RlOiBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zOTc3ODM4Ny9yLWRhdGFmcmFtZS1vbmUtaG90LWVuY29kaW5nLW9mLWNvbHVtbi1jb250YWluaW5nLW11bHRpcGxlLXRlcm1zCiAgZGYgPSBjYmluZChkZiwgbXRhYnVsYXRlKHN0cnNwbGl0KGRmW1tjb2x1bW5dXSwgIiwiKSkpCiAgY29scyA9IHVuaXF1ZSh1bmxpc3Qoc3Ryc3BsaXQoZGZbW2NvbHVtbl1dLCAnLCcpKSkKICB0b3BfY29scyA9IHNvcnQoY29sU3VtcyhkZltjb2xzXSksIGRlY3JlYXNpbmc9VFJVRSkKICB0b3BfY29sc19uYW1lcyA9IG5hbWVzKHRvcF9jb2xzWzE6bnVtVG9wQ29sc10pCiAgbm9uX3RvcF9jb2xzX25hbWVzID0gc2V0ZGlmZihjb2xzLCB0b3BfY29sc19uYW1lcykKICAKICAjIFJlbW92ZSB0aGUgbm9uX3RvcF9jb2xzCiAgZGZbbm9uX3RvcF9jb2xzX25hbWVzXSA9IGxpc3QoTlVMTCkKICByZXR1cm4obGlzdCgnZGYnPWRmLAogICAgICAgICAgICAgICd0b3BfY29scyc9dG9wX2NvbHNfbmFtZXMpKSAKfQpgYGAKCmBgYHtyIHByb2Nlc3MtY2F0ZWdvcmljYWx9CiMgUHJvY2Vzc2luZyBHZW5yZSBDb2x1bW4gLS0gV29yayB0YWtlbiBmcm9tIFBSMQojIE5vdGU6IENoYW5nZSAnU2NpLUZpJyB0byAnU2NpRmknIGJlY2F1c2UgZGFzaGVzIGluIGNvbHVtbiBuYW1lcyBhcmUgcmVhbGx5IGFubm95aW5nIGluIFIKIyBBbHNvIGNoYW5naW5nIE4vQSBpbiBHZW5yZSB0byBuYSBzaW5jZSAvIGFyZSBhbHNvIHJlYWxseSBhbm5veWluZwpkZiRHZW5yZSA8LSBnc3ViKCdTY2ktRmknLCAnU2NpRmknLCBkZiRHZW5yZSkKCiMgUHJvY2Vzc2luZyBSYXRpbmcgQ29sdW1uIC0tIFdvcmsgdGFrZW4gZnJvbSBQUjEKZGYkcmF0ZWRfY2xlYW5lZCA9IGlmZWxzZShkZiRSYXRlZCAlaW4lIGMoJ0cnLCAnUEctMTMnLCAnUEcnLCAnUicsICdOQy0xNycpLCBkZiRSYXRlZCwgJ090aGVyJykKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCdQRy0xMycsICdQRzEzJywgZGYkcmF0ZWRfY2xlYW5lZCkKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCdOQy0xNycsICdOQzE3JywgZGYkcmF0ZWRfY2xlYW5lZCkKCiMgUHJlcHJvY2Vzc2luZyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMgdG8gcmVtb3ZlIE4vQQpkZiRBY3RvcnMgPC0gZ3N1YignTi9BJywgJ25hJywgZGYkQWN0b3JzKQpkZiRDb3VudHJ5IDwtIGdzdWIoJ04vQScsICduYScsIGRmJENvdW50cnkpCmRmJERpcmVjdG9yIDwtIGdzdWIoJ04vQScsICduYScsIGRmJERpcmVjdG9yKQpkZiRHZW5yZSA8LSBnc3ViKCdOL0EnLCAnbmEnLCBkZiRHZW5yZSkKZGYkV3JpdGVyIDwtIGdzdWIoJ04vQScsICduYScsIGRmJFdyaXRlcikKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCdOL0EnLCAnbmEnLCBkZiRyYXRlZF9jbGVhbmVkKQoKIyBQcmVwcm9jZXNzaW5nIENhdGVnb3JpY2FsIFZhcmlhYmxlcyB0byByZW1vdmUgc3BhY2VzCmRmJEFjdG9ycyA8LSBnc3ViKCcgJywgJycsIGRmJEFjdG9ycykKZGYkQ291bnRyeSA8LSBnc3ViKCcgJywgJycsIGRmJENvdW50cnkpCmRmJERpcmVjdG9yIDwtIGdzdWIoJyAnLCAnJywgZGYkRGlyZWN0b3IpCmRmJEdlbnJlIDwtIGdzdWIoJyAnLCAnJywgZGYkR2VucmUpCmRmJFdyaXRlciA8LSBnc3ViKCcgJywgJycsIGRmJFdyaXRlcikKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCcgJywgJycsIGRmJHJhdGVkX2NsZWFuZWQpCgojIFByZXByb2Nlc3NpbmcgdG8gcmVtb3ZlIHBhcmFudGhlc2VzCmRmJFdyaXRlciA8LSBnc3ViKCdcXCgnLCAnJywgZGYkV3JpdGVyKQpkZiRXcml0ZXIgPC0gZ3N1YignXFwpJywgJycsIGRmJFdyaXRlcikKCiMgQ3JlYXRpbmcgb25lLWhvdCBlbmNvZGVkIENhdGVnb3JpY2FsIGNvbHVtbnMKZ2VucmVfb25lX2hvdF9lbmNvZGVkID0gY3JlYXRlX29uZV9ob3RfZW5jb2RlZChkZiwgJ0dlbnJlJywgMTApCmRmID0gZ2VucmVfb25lX2hvdF9lbmNvZGVkJGRmCnRvcF9nZW5yZXMgPSBnZW5yZV9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCmFjdG9yX29uZV9ob3RfZW5jb2RlZCA9IGNyZWF0ZV9vbmVfaG90X2VuY29kZWQoZGYsICdBY3RvcnMnLCAyMCkKZGYgPSBhY3Rvcl9vbmVfaG90X2VuY29kZWQkZGYKdG9wX2FjdG9ycyA9IGFjdG9yX29uZV9ob3RfZW5jb2RlZCR0b3BfY29scwoKZGlyZWN0b3Jfb25lX2hvdF9lbmNvZGVkID0gY3JlYXRlX29uZV9ob3RfZW5jb2RlZChkZiwgJ0RpcmVjdG9yJywgMjApCmRmID0gZGlyZWN0b3Jfb25lX2hvdF9lbmNvZGVkJGRmCnRvcF9kaXJlY3RvcnMgPSBkaXJlY3Rvcl9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCmNvdW50cnlfb25lX2hvdF9lbmNvZGVkID0gY3JlYXRlX29uZV9ob3RfZW5jb2RlZChkZiwgJ0NvdW50cnknLCAxMCkKZGYgPSBjb3VudHJ5X29uZV9ob3RfZW5jb2RlZCRkZgp0b3BfY291bnRyaWVzID0gY291bnRyeV9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCndyaXRlcl9vbmVfaG90X2VuY29kZWQgPSBjcmVhdGVfb25lX2hvdF9lbmNvZGVkKGRmLCAnV3JpdGVyJywgMjApCmRmID0gd3JpdGVyX29uZV9ob3RfZW5jb2RlZCRkZgp0b3Bfd3JpdGVycyA9IHdyaXRlcl9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCnJhdGluZ19vbmVfaG90X2VuY29kZWQgPSBjcmVhdGVfb25lX2hvdF9lbmNvZGVkKGRmLCAncmF0ZWRfY2xlYW5lZCcsIDUpCmRmID0gcmF0aW5nX29uZV9ob3RfZW5jb2RlZCRkZgp0b3BfcmF0aW5ncyA9IHJhdGluZ19vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCiMgUmVtb3ZlIGNvbHVtbnMgZnJvbSBEYXRhRnJhbWUKZGYkR2VucmUgPSBOVUxMCmRmJERpcmVjdG9yID0gTlVMTApkZiRBY3RvcnMgPSBOVUxMCmRmJENvdW50cnkgPSBOVUxMCmRmJFJhdGluZyA9IE5VTEwKYGBgCgoKYGBge3IgcHJvY2Vzcy1hd2FyZHN9CiMgUHJvY2Vzc2luZyBBd2FyZHMgY29sdW1uCiMgV29yayB0YWtlbiBmcm9tIFBSMQpjb2xzID0gYygnVGl0bGUnLCAnQXdhcmRzJywgJ0dyb3NzJykKZGY2ID0gZGZbY29sc10KCmF3YXJkcyA9IHN0cmlfZXh0cmFjdF9hbGwoZGY2JEF3YXJkcywgcmVnZXg9IlxcZCsiKQphd2FyZF9sYWJlbHMgPSBzdHJpX2V4dHJhY3RfYWxsKHRvbG93ZXIoZGY2JEF3YXJkcyksIHJlZ2V4PSJ3aW58d29ufHdpbnN8bm9taW4iLCBpZ25vcmUuY2FzZT1UUlVFKQoKd2luc19saXN0ID0gYygpCm5vbWluYXRpb25zX2xpc3QgPSBjKCkKCmZvciAoaSBpbiAxOmxlbmd0aChhd2FyZHMpKXsKICAgIHdpbnMgPSAwCiAgICBub21pbmF0aW9ucyA9IDAKICAgIGZvcihqIGluIDE6bGVuZ3RoKGF3YXJkc1tbaV1dKSl7CiAgICAgICAgaWYoIWlzLm5hKGF3YXJkX2xhYmVsc1tbaV1dW2pdKSAmIChhd2FyZF9sYWJlbHNbW2ldXVtqXSA9PSAnd2lucycgfCBhd2FyZF9sYWJlbHNbW2ldXVtqXSA9PSAnd2luJyB8IGF3YXJkX2xhYmVsc1tbaV1dW2pdID09ICd3b24nKSl7CiAgICAgICAgICAgIHdpbnMgPSB3aW5zICsgYXMubnVtZXJpYyhhd2FyZHNbW2ldXVtqXSkKICAgICAgICB9IGVsc2UgaWYgKCFpcy5uYShhd2FyZF9sYWJlbHNbW2ldXVtqXSkgJiBhd2FyZF9sYWJlbHNbW2ldXVtqXSA9PSAnbm9taW4nKXsKICAgICAgICAgICAgbm9taW5hdGlvbnMgPSBub21pbmF0aW9ucyArIGFzLm51bWVyaWMoYXdhcmRzW1tpXV1bal0pCiAgICAgICAgfQogICAgfQogICAgd2luc19saXN0ID0gYyh3aW5zX2xpc3QsIHdpbnMpCiAgICBub21pbmF0aW9uc19saXN0ID0gYyhub21pbmF0aW9uc19saXN0LCBub21pbmF0aW9ucykKfQoKZGYkd2lucyA9IHdpbnNfbGlzdApkZiRub21pbmF0aW9ucyA9IG5vbWluYXRpb25zX2xpc3QKYGBgCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQojIFRPRE86IEJ1aWxkICYgZXZhbHVhdGUgbW9kZWwgMyAoY29udmVydGVkIG5vbi1udW1lcmljIHZhcmlhYmxlcyBvbmx5KQojICJSZWNyZWF0ZSIgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRmIHdpdGggdGhlIHNhbWUgaW5kaWNlcyB0byBpbmNsdWRlIHRoZSBuZXcgY29sdW1ucwp0cmFpbl9kZiA8LSBkZlt0cmFpbl9pZHgsIF0KdGVzdF9kZiAgPC0gZGZbLXRyYWluX2lkeCwgXQoKIyBUdXJuaW5nIGEgdmVjdG9yIG9mIGNvbHVtbnMgaW4gYSBtb2RlbCBleHByZXNzaW9uOiBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy83OTg2ODA1L2hvdy1kby1pLXJ1bi1hLW11bHRpcGxlLWxpbmVhci1yZWdyZXNzaW9uLXVzaW5nLWEtdmVjdG9yLWFzLW15LXByZWRpY3RvcnMKbW9kZWwzX2NvbHMgPSBjKHRvcF9nZW5yZXMsIHRvcF9kaXJlY3RvcnMsIHRvcF9hY3RvcnMsIHRvcF9jb3VudHJpZXMsIHRvcF93cml0ZXJzLCB0b3BfcmF0aW5ncywgYygnd2lucycsICdub21pbmF0aW9ucycpKQptb2RlbDNfZXhwID0gcGFzdGUoJ0dyb3NzficsIHBhc3RlKG1vZGVsM19jb2xzLCBjb2xsYXBzZT0nKycpKQoKZXZhbF9tb2RlbDMgPSBldmFsX21vZGVsKHRyYWluX2RmLCB0ZXN0X2RmLCBtb2RlbDNfZXhwLCB0cmFpbl9zaXplcywgbnVtX3JlcGVhdD0xMCkKZGZfZXZhbF9tb2QzID0gZXZhbF9tb2RlbDMkZGZfZXZhbApgYGAKCioqUSoqOiBFeHBsYWluIHdoaWNoIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB5b3UgdXNlZCwgYW5kIGhvdyB5b3UgZW5jb2RlZCB0aGVtIGludG8gZmVhdHVyZXMuCgoqKkEqKjogCgpUaGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRoYXQgSSB1c2VkIHdlcmUKCjEuIEdlbnJlcyAodG9wIDEwKQoyLiBEaXJlY3RvcnMgKHRvcCAyMCkKMy4gQWN0b3JzICh0b3AgMjApCjQuIENvdW50cmllcyAodG9wIDEwKQo1LiBXcml0ZXJzICh0b3AgMjApCjYuIEF3YXJkcyAocHJvY2Vzc2VkIGludG8gd2lucyBhbmQgbm9taW5hdGlvbnMpCjcuIFJhdGluZ3MgKGUuZy4gUiwgUEcxMywgUEcsIEc7IHRvcCA1KQoKVGhlIHJlYXNvbiBmb3IgdGhlc2UgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdoeSBJIG9ubHkgdXNlIHRoZSB0b3AgMTAvMjAgaXMgYmVjYXVzZSBvZiBjdXJzZSBvZiBkaW1lbnNpb25hbGl0eS4gSWYgSSBpbmNsdWRlZCBhbGwgYXMgZmVhdHVyZXMsIEkgd291bGQgaGF2ZSB0b28gbXVjaCB2YXJpYXRpb24gKGFsYSBiaWFzLXZhcmlhbmNlIHRyYWRlb2ZmKS4gVG8gZW5jb2RlIHRoZW0gaW50byBmZWF0dXJlcywgZm9yIGVhY2ggZW50aXR5IChlLmcuIGdlbnJlLCBkaXJlY3RvciwgYWN0b3IpIEkgY3JlYXRlZCBhIG5ldyBvbmUtaG90IGVuY29kZWQgYmluYXJ5IHZhcmlhYmxlLiBFYWNoIHZhcmlhYmxlIHdvdWxkIGVxdWFsIDEgaWYgdGhlIG1vdmllIGNvbnRhaW5lZCB0aGF0IGVudGl0eSwgZWxzZSAwLiBQbGVhc2Ugc2VlIHRoZSBoZWxwZXIgZnVuY3Rpb24gSSBkZWZpbmVkIGBjcmVhdGVfb25lX2hvdF9lbmNvZGVkYCBmb3IgdGhlIGNvZGUgdGhhdCBJIHVzZWQuIAoKCioqUSoqOiBXaGF0IGlzIHRoZSBiZXN0IG1lYW4gdGVzdCBSTVNFIHZhbHVlIHlvdSBvYnNlcnZlZCwgYW5kIGF0IHdoYXQgdHJhaW5pbmcgc2V0IHNpemU/IEhvdyBkb2VzIHRoaXMgY29tcGFyZSB3aXRoIFRhc2sgMj8KCioqQSoqOiAKCmBgYHtyfQojIEJlc3QgdGVzdCBSTVNFCmJlc3RfdGVzdHJtc2VfbW9kMyA9IGV2YWxfbW9kZWwzJGJlc3RfdGVzdF9ybXNlCmJlc3RfdHJhaW5fc2l6ZV9tb2QzID0gZXZhbF9tb2RlbDMkYmVzdF90cmFpbl9zaXplCmBgYAoKVGhlIGJlc3QgbWVhbiB0ZXN0IFJNU0UgdmFsdWUgSSBvYnNlcnZlZCB3YXMgYHIge2Jlc3RfdGVzdHJtc2VfbW9kM31gIGFuZCBJIG9ic2VydmVkIHRoYXQgYXQgdGhlIHRyYWluaW5nIHNldCBzaXplIGByIHtiZXN0X3RyYWluX3NpemVfbW9kMyAqIDEwMH1gIHBlcmNlbnQuIE5vdGljZSBmcm9tIHRoZSBncmFwaCBhYm92ZSBhcyB3ZWxsIGZvciBtb2RlbCAzIHRoYXQgd2Ugc2VlIHRoYXQgVGVzdCBSTVNFIGdlbmVyYWxseSBkZWNyZWFzZXMgd2l0aCBoaWdoZXIgdHJhaW5pbmcgc2V0LiBBZ2FpbiwgdGhpcyBtYWtlcyBzZW5zZSBiZWNhdXNlIHdpdGggdmVyeSBsb3cgdHJhaW5pbmcgc2V0IHNpemUsIHRoZSBtb2RlbCB3aWxsIG92ZXJmaXQgdGhlIG1vcmUgbGltaXRlZCBkYXRhLgoKV2UgY2FuIGFsc28gY29tcGFyZSB0aGUgYmVzdCB0ZXN0IFJNU0UgZGlyZWN0bHkgYmV0d2VlbiBNb2RlbHMgMSwgMiwgYW5kIDMKCmBgYHtyfQpnZ3Bsb3QoZGF0YT1kZl9ldmFsX21vZDMsIGFlcyh4PXRyYWluX3NpemUsIHk9cm1zZXMpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1ybXNlX3R5cGUsIGdyb3VwPXJtc2VfdHlwZSkpICsKICBnZ3RpdGxlKCdNb2RlbCAzOiBUcmFpbmluZyBhbmQgVGVzdCBSTVNFJykKCnByaW50KGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlKQpwcmludChldmFsX21vZGVsMiRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDMkYmVzdF90ZXN0X3Jtc2UpCmBgYAoKTm90aWNlIHRoYXQgY29tcGFyZWQgdG8gTW9kZWwgMiwgTW9kZWwgMyBwZXJmb3JtZWQgbXVjaCB3b3JzZS4gVGhpcyBpcyBiZWNhdXNlIHdlIG9ubHkgdXNlZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgZm9yIE1vZGVsIDMgYW5kIHdlIGhhdmUgYSBwcmV0dHkgc3BhcnNlIGRhdGEgc2V0IGp1c3QgdXNpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBUaGlzIGFsc28gcG90ZW50aWFsbHkgc3VnZ2VzdHMgdGhhdCBqdXN0IHVzaW5nIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaXMgbm90IHN1ZmZpY2llbnQsIHdlIG5lZWQgdG8gY29tYmluZSBudW1lcmljYWwgYW5kIGV2ZW4gaW50ZXJhY3QgbnVtZXJpY2FsIGFuZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdG8gY29tZSB1cCB3aXRoIGFuIGV2ZW4gYmV0dGVyIG1vZGVsLgoKCiMjIDQuIE51bWVyaWMgYW5kIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwoKVHJ5IHRvIGltcHJvdmUgdGhlIHByZWRpY3Rpb24gcXVhbGl0eSBhcyBtdWNoIGFzIHBvc3NpYmxlIGJ5IHVzaW5nIGJvdGggbnVtZXJpYyBhbmQgbm9uLW51bWVyaWMgdmFyaWFibGVzIGZyb20gKipUYXNrcyAyICYgMyoqLgoKYGBge3Igd2FybmluZz1GQUxTRX0KIyBUT0RPOiBCdWlsZCAmIGV2YWx1YXRlIG1vZGVsIDQgKG51bWVyaWMgJiBjb252ZXJ0ZWQgbm9uLW51bWVyaWMgdmFyaWFibGVzKQptb2RlbDRfY29scyA9IHVuaXF1ZShjKG1vZGVsMl9jb2xzLCBtb2RlbDNfY29scykpCm1vZGVsNF9leHAgPSBwYXN0ZSgnR3Jvc3N+JywgcGFzdGUobW9kZWw0X2NvbHMsIGNvbGxhcHNlPScrJykpCgpldmFsX21vZGVsNCA9IGV2YWxfbW9kZWwodHJhaW5fZGYsIHRlc3RfZGYsIG1vZGVsNF9leHAsIHRyYWluX3NpemVzLCBudW1fcmVwZWF0PTEwKQpkZl9ldmFsX21vZDQgPSBldmFsX21vZGVsNCRkZl9ldmFsCmBgYAoKKipRKio6IENvbXBhcmUgdGhlIG9ic2VydmVkIFJNU0Ugd2l0aCBUYXNrcyAyICYgMy4KCioqQSoqOiAKRm9yIE1vZGVsIDQsIEkgc2ltcGx5IGNvbWJpbmVkIChidXQgbm8gYWRkaXRpb25hbCBpbnRlcmFjdGlvbnMpIHRoZSB0ZXJtcyBmcm9tIFRhc2sgMiBhbmQgMyB0b2dldGhlci4gVGhlIFRyYWluaW5nIHZzIFRlc3QgUk1TRSBmb3IgTW9kZWwgNCBpcyBzaG93biBhYm92ZS4gSW4gYWRkaXRpb24sIHdlIGNhbiBkaXJlY3RseSBjb21wYXJlIHRoZSBiZXN0IHRlc3QgUk1TRXM6CgpgYGB7cn0KZ2dwbG90KGRhdGE9ZGZfZXZhbF9tb2Q0LCBhZXMoeD10cmFpbl9zaXplLCB5PXJtc2VzKSkgKwogIGdlb21fbGluZShhZXMoY29sb3I9cm1zZV90eXBlLCBncm91cD1ybXNlX3R5cGUpKSArCiAgZ2d0aXRsZSgnTW9kZWwgNDogVHJhaW5pbmcgYW5kIFRlc3QgUk1TRScpCgpwcmludChldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UpCnByaW50KGV2YWxfbW9kZWwzJGJlc3RfdGVzdF9ybXNlKQpwcmludChldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSkKYGBgCgpUaGUgaW1wcm92ZW1lbnQgb2YgTW9kZWwgNCBjb21wYXJlZCB0byBNb2RlbCAyIHdhczoKCmBgYHtyfQppbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNCA9IGV2YWxfbW9kZWw0JGJlc3RfdGVzdF9ybXNlIC0gZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UKcGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNCA9IDEuMCAqIGFicyhldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWwyJGJlc3RfdGVzdF9ybXNlKSAvIGV2YWxfbW9kZWwyJGJlc3RfdGVzdF9ybXNlCnByaW50KCdJbXByb3ZlbWVudCBpbiBSTVNFJykKcHJpbnQoaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDQpCgojIEFzIGEgcGVyY2VudGFnZQpwcmludChwZXJjZW50YWdlX2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWw0KQpgYGAKCk5vdGljZSB0aGF0IHRoZSBpbXByb3ZlbWVudCBpbiBNb2RlbCA0IHdhcyBgciB7aW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDR9YCB3aGljaCByZXByZXNlbnRzIGEgZnVydGhlciBgciB7cGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNCAqIDEwMH1gIHBlcmNlbnQgaW1wcm92ZW1lbnQgb3ZlciBNb2RlbCAyISBXaGlsZSB0aGlzIGltcHJvdmVtZW50IG1pZ2h0IHNlZW0gbW9kZXN0LCBhZ2FpbiwgaW4gUGFydCA1LCB3ZSBleHBsb3JlIGhvdyB0byBpbXByb3ZlIHRoaXMgZXZlbiBmdXJ0aGVyIHdpdGggbWVhbmluZ2Z1bCBpbnRlcmFjdGlvbnMuIEZ1cnRoZXJtb3JlLCBub3RlIHRoYXQgdGhlcmUgaXMgYSBtYXNzaXZlIGltcHJvdmVtZW50IG92ZXIgUGFydCAzIHdoZXJlIHdlIGp1c3QgaGFkIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gCgpgYGB7cn0KIyBCZXN0IHRlc3QgUk1TRQpiZXN0X3Rlc3RybXNlX21vZDQgPSBldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZQpiZXN0X3RyYWluX3NpemVfbW9kNCA9IGV2YWxfbW9kZWw0JGJlc3RfdHJhaW5fc2l6ZQpgYGAKClRoZSBiZXN0IG1lYW4gdGVzdCBSTVNFIHZhbHVlIEkgb2JzZXJ2ZWQgZm9yIE1vZGVsIDQgd2FzIGByIHtiZXN0X3Rlc3RybXNlX21vZDR9YCBhbmQgSSBvYnNlcnZlZCB0aGF0IGF0IHRoZSB0cmFpbmluZyBzZXQgc2l6ZSBgciB7YmVzdF90cmFpbl9zaXplX21vZDQgKiAxMDB9YCBwZXJjZW50LiBOb3RpY2UgYWdhaW4sIHdlIHNlZSBhIHNpbWlsYXIgcGF0dGVybiBhcyBvdGhlciBtb2RlbHMuIEZyb20gdGhlIGdyYXBoIGFib3ZlLCBub3RpY2UgdGhhdCBmb3IgTW9kZWwgNCB0aGF0IHdlIHNlZSB0aGF0IFRlc3QgUk1TRSBnZW5lcmFsbHkgZGVjcmVhc2VzIHdpdGggaGlnaGVyIHRyYWluaW5nIHNldC4gQWdhaW4sIHRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aXRoIHZlcnkgbG93IHRyYWluaW5nIHNldCBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBtb3JlIGxpbWl0ZWQgZGF0YS4KCiMjIDUuIEFkZGl0aW9uYWwgZmVhdHVyZXMKCk5vdyB0cnkgY3JlYXRpbmcgYWRkaXRpb25hbCBmZWF0dXJlcyBzdWNoIGFzIGludGVyYWN0aW9ucyAoZS5nLiBgaXNfZ2VucmVfY29tZWR5YCB4IGBpc19idWRnZXRfZ3JlYXRlcl90aGFuXzNNYCkgb3IgZGVlcGVyIGFuYWx5c2lzIG9mIGNvbXBsZXggdmFyaWFibGVzIChlLmcuIHRleHQgYW5hbHlzaXMgb2YgZnVsbC10ZXh0IGNvbHVtbnMgbGlrZSBgUGxvdGApLgoKYGBge3Igd2FybmluZz1GQUxTRX0KIyBUT0RPOiBCdWlsZCAmIGV2YWx1YXRlIG1vZGVsIDUgKG51bWVyaWMsIG5vbi1udW1lcmljIGFuZCBhZGRpdGlvbmFsIGZlYXR1cmVzKQoKIyBDcmVhdGluZyBpbnRlcmFjdGlvbiB2YXJpYWJsZXMgYmV0d2VlbiB2YXJpYWJsZXMsIGFzIGV4cGxvcmVkIGFuZCBkaXNjdXNzZWQgaW4gUGFydCAyCmRmJGltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1X2ltZGJSYXRpbmcgPSBkZiRpbWRiUmF0aW5nX2dyZWF0ZXJfdGhhbl83NSAqIGRmJGltZGJSYXRpbmcKZGYkaW1kYlJhdGluZ19ncmVhdGVyX3RoYW5fNzVfaW1kYlJhdGluZzIgPSBkZiRpbWRiUmF0aW5nX2dyZWF0ZXJfdGhhbl83NSAqIGRmJGltZGJSYXRpbmcyCmRmJGlzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtX2J1ZGdldCA9IGRmJGlzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtICogZGYkQnVkZ2V0CmRmJFJ1bnRpbWVfUnVudGltZVVuZGVyNzUgPSBkZiRSdW50aW1lICogZGYkUnVudGltZVVuZGVyNzUKZGYkUnVudGltZV9SdW50aW1lQmV0d2Vlbjc1XzEyNSA9IGRmJFJ1bnRpbWUgKiBkZiRSdW50aW1lQmV0d2Vlbjc1XzEyNQpkZiRSdW50aW1lX1J1bnRpbWVHcmVhdGVyMTI1ID0gZGYkUnVudGltZSAqIGRmJFJ1bnRpbWVHcmVhdGVyMTI1CmRmJHJlbGVhc2VkX3llYXJfZWFybHlfMjAwMHMgPSBkZiRyZWxlYXNlZF95ZWFyICogZGYkZWFybHlfMjAwMHMKZGYkcmVsZWFzZWRfeWVhcl9taWRfMjAwMHMgPSBkZiRyZWxlYXNlZF95ZWFyICogZGYkbWlkXzIwMDBzCmRmJHJlbGVhc2VkX3llYXJfcG9zdF8yMDEwID0gZGYkcmVsZWFzZWRfeWVhciAqIGRmJHBvc3RfMjAxMAoKIyBDcmVhdGluZyBGYW1pbHkgRnJpZW5kbHkgdmFyaWFibGUgdGhhdCBpcyBHLCBQRywgb3IgUEcxMy4gQWxzbyBhZGRpbmcgaW50ZXJhY3Rpb25zLiAKZGYkZmFtaWx5X2ZyaWVuZGx5ID0gYXMubnVtZXJpYyhkZiRHICsgZGYkUEcgKyBkZiRQRzEzID49IDEpCmRmJGZhbWlseV9mcmllbmRseV93aW5zID0gZGYkZmFtaWx5X2ZyaWVuZGx5ICogZGYkd2lucwpkZiRmYW1pbHlfZnJpZW5kbHlfbm9taW5hdGlvbnMgPSBkZiRmYW1pbHlfZnJpZW5kbHkgKiBkZiRub21pbmF0aW9ucwoKIyBNYWRlIHRvIERWRApkZiRtYWRlX3RvX2R2ZCA9IGFzLm51bWVyaWMoIWlzLm5hKGRmJERWRCkpCgojIE51bWJlciBvZiBMYW5ndWFnZXMKZGYkbnVtYmVyX29mX2xhbmd1YWdlcyA9IHNhcHBseShzdHJzcGxpdChkZltbJ0xhbmd1YWdlJ11dLCAiLCIpLCBsZW5ndGgpCgojICJSZWNyZWF0ZSIgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRmIHdpdGggdGhlIHNhbWUgaW5kaWNlcyB0byBpbmNsdWRlIHRoZSBuZXcgY29sdW1ucwp0cmFpbl9kZiA8LSBkZlt0cmFpbl9pZHgsIF0KdGVzdF9kZiAgPC0gZGZbLXRyYWluX2lkeCwgXQoKYWRkaXRpb25hbF9tb2RlbDVfY29scyA9IGMoJ2ltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1X2ltZGJSYXRpbmcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnaW1kYlJhdGluZ19ncmVhdGVyX3RoYW5fNzVfaW1kYlJhdGluZzInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnaXNfYnVkZ2V0X2dyZWF0ZXJfdGhhbl82MG1fYnVkZ2V0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1J1bnRpbWVfUnVudGltZVVuZGVyNzUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnUnVudGltZV9SdW50aW1lQmV0d2Vlbjc1XzEyNScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdSdW50aW1lX1J1bnRpbWVHcmVhdGVyMTI1JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3JlbGVhc2VkX3llYXJfZWFybHlfMjAwMHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAncmVsZWFzZWRfeWVhcl9taWRfMjAwMHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAncmVsZWFzZWRfeWVhcl9wb3N0XzIwMTAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnZmFtaWx5X2ZyaWVuZGx5JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2ZhbWlseV9mcmllbmRseV93aW5zJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2ZhbWlseV9mcmllbmRseV9ub21pbmF0aW9ucycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdtYWRlX3RvX2R2ZCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdudW1iZXJfb2ZfbGFuZ3VhZ2VzJykKbW9kZWw1X2NvbHMgPSB1bmlxdWUoYyhtb2RlbDRfY29scywgYWRkaXRpb25hbF9tb2RlbDVfY29scykpCm1vZGVsNV9leHAgPSBwYXN0ZSgnR3Jvc3N+JywgcGFzdGUobW9kZWw1X2NvbHMsIGNvbGxhcHNlPScrJykpCgpldmFsX21vZGVsNSA9IGV2YWxfbW9kZWwodHJhaW5fZGYsIHRlc3RfZGYsIG1vZGVsNV9leHAsIHRyYWluX3NpemVzLCBudW1fcmVwZWF0PTEwKQpkZl9ldmFsX21vZDUgPSBldmFsX21vZGVsNSRkZl9ldmFsCmBgYAoKKipRKio6IEV4cGxhaW4gd2hhdCBuZXcgZmVhdHVyZXMgeW91IGRlc2lnbmVkIGFuZCB3aHkgeW91IGNob3NlIHRoZW0uCgoqKkEqKjogCkluIGFkZGl0aW9uIHRvIHRoZSBmZWF0dXJlcyBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0cywgSSBjcmVhdGVkIHRoZSBmb2xsb3dpbmcgbmV3IGludGVyYWN0aW9uIGZlYXR1cmVzLiBOb3RlIHRoYXQgdGhlIHJlYXNvbmluZyBiZWhpbmQgdGhlc2UgaW50ZXJhY3Rpb25zIHdlcmUgYWxyZWFkeSBwYXJ0bHkgZXhwbG9yZWQgaW4gUGFydCAyLCBhbmQgSSB3aWxsIGVsYWJvcmF0ZSBmdXJ0aGVyIGhlcmUKCjEuIEludGVyYWN0aW9uIG9mIGBpbWRiUmF0aW5nYCBhbmQgYGltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1YAoyLiBJbnRlcmFjdGlvbiBvZiBgaW1kYlJhdGluZzJgIGFuZCBgaW1kYlJhdGluZ19ncmVhdGVyX3RoYW5fNzVgCjMuIEludGVyYWN0aW9uIG9mIGBCdWRnZXRgIGFuZCBgaXNfYnVkZ2V0X2dyZWF0ZXJfdGhhbl82MG1gCjQuIEludGVyYWN0aW9uIG9mIGBSdW50aW1lYCBhbmQgYFJ1bnRpbWVVbmRlcjc1YCwgYFJ1bnRpbWVCZXR3ZWVuNzVfMTI1YCwgYW5kIGBSdW50aW1lR3JlYXRlcjEyNWAKNS4gSW50ZXJhY3Rpb24gb2YgYFJ1bnRpbWUyYCBhbmQgYFJ1bnRpbWVVbmRlcjc1YCwgYFJ1bnRpbWVCZXR3ZWVuNzVfMTI1YCwgYW5kIGBSdW50aW1lR3JlYXRlcjEyNWAKNi4gSW50ZXJhY3Rpb24gb2YgYHJlbGVhc2VkX3llYXJgIGFuZCBgZWFybHlfMjAwMHNgLCBgbWlkXzIwMDBzYCwgYW5kIGBwb3N0XzIwMTBgCgpGdXJ0aGVybW9yZSwgSSBleHBsb3JlIG90aGVyIHJlbGF0aW9uc2hpcHMgdGhhdCB3ZXJlIGVudGlyZWx5IHByZXZpb3VzbHkgdW5kaXNjdXNzZWQgc3VjaCBhcwoKNy4gYGZhbWlseV9mcmllbmRseWA6IERlZmluZWQgYXMgTVBBQSByYXRpbmcgb2YgRywgUEcsIG9yIFBHLTEzCjguIGBmYW1pbHlfZnJpZW5kbHlfd2luc2A6IEludGVyYWN0aW9uIG9mIGBmYW1pbHlfZnJpZW5kbHlgIGFuZCBgd2luc2AKOS4gYGZhbWlseV9mcmllbmRseV9ub21pbmF0aW9uc2A6IEludGVyYWN0aW9uIG9mIGBmYW1pbHlfZnJpZW5kbHlgIGFuZCBgbm9taW5hdGlvbnNgLgoxMC4gYG1hZGVfdG9fZHZkYDogSW5kaWNhdG9yIHZhcmlhYmxlIGZvciBpZiBtb3ZpZSB3YXMgbWFkZSB0byBEVkQsIGludHVpdGlvbiBiZWluZyBpZiBtYWRlIHRvIERWRCBwZXJoYXBzIGl0IHdhcyBhIHN1Y2Nlc3NmdWwgbW92aWUgYW5kIEdyb3NzZWQgbW9yZS4KMTEuIGBudW1iZXJfb2ZfbGFuZ3VhZ2VzYDogUGFyc2VkIHRoZSBgTGFuZ3VhZ2VgIGNvbHVtbiBhbmQgY291bnQgbnVtYmVyIG9mIGxhbmd1YWdlcyBtb3ZpZSBpcyBtYWRlLiBJbnR1aXRpb24gYmVpbmcgaWYgbXVsdGlwbGUgbGFuZ3VhZ2VzLCBpdCBpcyBhbiBpbnRlcm5hdGlvbmFsIHJlbGVhc2Uvc3VjY2VzcyBhbmQgdGh1cyBHcm9zc2VkIG1vcmUuCgpGb3IgdGhlIGZpcnN0IDYgaW50ZXJhY3Rpb25zLCB3ZSBleHBsb3JlZCB0aGVzZSBhIGJpdCBpbiBQYXJ0IDIsIGJ1dCBJJ2xsIGVsYWJvcmF0ZSBmdXJodGVyIGhlcmUuIFJlY2FsbCB0aGF0IGluIFBhcnQgMiwgSSBjcmVhdGVkIGEgYmlubmVkIHZhcmlhYmxlIGZvciBpbWRiUmF0aW5nIGF0IDcuNS4gTm93LCBJIHdpbGwgaW50ZXJhY3QgdGhpcyBiaW5uZWQgdmFyaWFibGUgd2l0aCBgaW1kYlJhdGluZ2AgYW5kIGBpbWRiUmF0aW5nMmAgdG8gY2FwdHVyZSBhIGRpZmZlcmVudCByZWxhdGlvbnNoaXAgYWZ0ZXIgNy41IChtb3JlIG9uIHRoaXMgYmVsb3cpLgoKSWYgd2UgcmUtbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYGltZGJSYXRpbmdgIGFuZCBgR3Jvc3NgLCB3ZSBwbG90IHRoZSBhdmVyYWdlIEdyb3NzIGdpdmVuIGltZGJSYXRpbmcsIGFuZCBub3RpY2UgdGhhdCB0aGUgcmVsYXRpb25zaGlwIHNlZW1zIHRvIGJlIGxpbmVhciB1bnRpbCBhYm91dCA3LjUuIFRoZW4gdGhlIGF2ZXJhZ2UgZ3Jvc3MgaW5jcmVhc2VzIGEgbG90LiBUaGlzIGlzIGFuIGludGVyZXN0aW5nIHJlbGF0aW9uc2hpcCBiZWNhdXNlIG1heWJlIG1vdmllcyBiZWxvdyA3LjUgcmF0aW5nIGFyZSBtb3N0bHkgbm90IHRoYXQgZ3JlYXQgYW5kIHNvIHRoZSByZWxhdGlvbnNoaXAgaXMgbGluZWFyLiBCdXQgaWYgYSBtb3ZpZSBpcyByZWFsbHkgcmVhbGx5IGdvb2QsIHdoZW4gdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEdyb3NzIGFuZCByYXRpbmcgY2hhbmdlcywgdGhhdCBpcyBhIHJlYWxseSByZWFsbHkgZ29vZCBtb3ZpZSBncm9zc2VzIG11Y2ggbXVjaCBtb3JlLiBUaHVzLCBJIGNob3NlIHRvIGNyZWF0ZSBhIGJpbm5lZCB2YXJpYWJsZSBhdCBgaW1kYlJhdGluZyA9IDcuNWAgYW5kIGludGVyYWN0ZWQgaXQgd2l0aCBgaW1kYlJhdGluZ2AgYW5kIGBpbWRiUmF0aW5nMmAgdG8gdHJ5IHRvIGNhcHR1cmUgYSBkaWZmZXJlbnQgcmVsYXRpb25zaGlwIGFmdGVyIGBpbWRiUmF0aW5nID0gNy41YCwgd2hlcmUgYGltZGJSYXRpbmcyYCBpcyB0aGUgc3F1YXJlZCB0ZXJtIG9mIGltZGJSYXRpbmcgKHNpbmNlIHRoZSByZWxhdGlvbnNoaXAgYWZ0ZXIgaW1kYlJhdGluZz03LjUgbG9va3MgZXhwb25lbnRpYWwpLgoKYGBge3J9CmdncGxvdChkZiwgYWVzKGltZGJSYXRpbmcsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikKYGBgCgpTZWNvbmQsIEkgbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gQnVkZ2V0IGFuZCBHcm9zcy4gVGhpcyBpcyBiZWNhdXNlIGlmIHdlIHJlbWVtYmVyIGJhY2sgdG8gUHJvamVjdCAxLCBoaWdoZXIgYnVkZ2V0IGZpbG1zIHRlbmRlZCB0byBncm9zcyBtdWNoIGhpZ2hlci4gTG9va2luZyBhdCB0aGUgZ3JhcGhzIGJlbG93LCBpdCBzZWVtcyBsaWtlICQ2ME0gd291bGQgYmUgYW4gaW50ZXJlc3RpbmcgYmluLCBhbmQgSSB3YW50ZWQgdG8gY2FwdHVyZSB0aGUgaW50ZXJhY3Rpb24gb2YgdGhlIGJpbm5lZCB2YXJpYWJsZSB3aXRoIGBCdWRnZXRgIHRvIGFsbG93IGZvciBhIGRpZmZlcmVuY2UgaW4gc2xvcGUgYWZ0ZXIgJDYwTS4gCgpgYGB7cn0KIyBEaXN0cmlidXRpb24gb2YgR3Jvc3MgYnkgQnVkZ2V0CiMgTm93IHdlIG5lZWQgdG8gc2hvdyBkaXN0cmlidXRpb24gb2YgcnVudGltZSBieSBCdWRnZXQuIERvIGEgYm94cGxvdCBncm91cGVkIGJ5IGJ1ZGdldApkZiRidWRnZXRfcm91bmRlZCA9IHJvdW5kX2FueShkZiRCdWRnZXQsIDEwMDAwMDAwLCBmID0gZmxvb3IpCmRmJGJ1ZGdldF9yb3VuZGVkW2RmJEJ1ZGdldCA+PSA2MDAwMDAwMF0gPSA2MDAwMDAwMApkZiRidWRnZXRfcm91bmRlZCA9IGFzLmNoYXJhY3RlcihkZiRidWRnZXRfcm91bmRlZCkKZGYkYnVkZ2V0X3JvdW5kZWRbZGYkYnVkZ2V0X3JvdW5kZWQgPT0gJzYwMDAwMDAwMCddID0gJ092ZXIgNjBNJwoKZ2dwbG90KGRmLCBhZXMoYXMuZmFjdG9yKGJ1ZGdldF9yb3VuZGVkKSwgR3Jvc3MpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgiQnVkZ2V0IFJvdW5kZWQiKSArCiAgZ2d0aXRsZSgnRGlzdHJpYnV0aW9uIG9mIEdyb3NzIGJ5IEJ1ZGdldCcpCgpnZ3Bsb3QoZGYsIGFlcyhCdWRnZXQsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikKYGBgCgpOZXh0LCB3aXRoIHJlZ2FyZHMgdG8gYFJ1bnRpbWVgIHJlY2FsbCBmcm9tIFBhcnQgMiB3ZSBzYXcgYW4gaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGJpbnMgb2YgcHJlLTc1LCBiZXR3ZWVuIDc1IGFuZCAxMjUsIGFuZCBhZnRlciAxMjUgKHJlcHJvZHVjZWQgdmlzdWFsIGJlbG93KS4gVG8gY2FwdHVyZSB0aGlzIGludGVyYWN0aW9uLCB3ZSBpbnRlcmFjdGVkIGBSdW50aW1lYCBhbmQgYFJ1bnRpbWUyYCB3aXRoIHRoZSBiaW5uZWQgdmFyaWFibGVzLgoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2dwbG90KGRmLCBhZXMoUnVudGltZSwgR3Jvc3MpKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDIsIGdlb20gPSAicG9pbnQiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTc1LCBjb2xvcj0nb3JhbmdlJywgbGluZXR5cGU9J2xvbmdkYXNoJykgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD0xMjUsIGNvbG9yPSdibHVlJywgbGluZXR5cGU9J2xvbmdkYXNoJykgKwogIGdndGl0bGUoJ0F2ZXJhZ2UgR3Jvc3MgdnMgUnVudGltZScpCmBgYAoKTmV4dCwgd2l0aCByZWdhcmRzIHRvIGByZWxlYXNlZF95ZWFyYCByZWNhbGwgZnJvbSBQYXJ0IDIgd2Ugc2F3IGFuIGludGVyZXN0aW5nIHJlbGF0aW9uc2hpcCwgcG90ZW50aWFsbHkgZXhwbGFpbmVkIGJ5IEVjb25vbWljIGJ1c2luZXNzIGN5Y2xlcy4gQmVsb3csIHdlIHJlcHJvZHVjZSB0aGUgdmlzdWFsaXphdGlvbiwgYW5kIHRvIGFsbG93IGZvciB0aGUgbW9kZWwgdG8gY2FwdHVyZSB0aGlzIGludGVyZXN0aW5nIGVmZmVjdCwgd2UgaW50ZXJhY3QgYHJlbGVhc2VkX3llYXJgIHdpdGggdGhlIGJpbm5lZCB2YXJpYWJsZXMuCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3QoZGYsIGFlcyhyZWxlYXNlZF95ZWFyLCBHcm9zcykpICsKICBnZW9tX3BvaW50KHN0YXQ9J3N1bW1hcnknKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTIwMDQuNSwgY29sb3I9J29yYW5nZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MjAwOS41LCBjb2xvcj0nYmx1ZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZ3RpdGxlKCdSZWxhdGlvbnNoaXAgYmV0d2VlbiBHcm9zcyBhbmQgUmVsZWFzZWQgWWVhcicpCmBgYAoKTmV4dCwgSSB3YW50ZWQgdG8gZXhwbG9yZSBNUEFBIFJhdGluZ3MgbW9yZSBiZWNhdXNlIEkgbm90aWNlZCBhIHByZXR0eSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIEdyb3NzIGJ5IGRpZmZlcmVudCBNUEFBIFJhdGluZ3MuIEluIHBhcnRpY3VsYXIsIHdlIGNhbiBzZWUgZnJvbSB0aGUgZ3JhcGggYmVsb3cgdGhhdDoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkZiwgYWVzKHJhdGVkX2NsZWFuZWQsIEdyb3NzKSkgKwogIGdlb21fYmFyKHN0YXQ9J3N1bW1hcnknKQpgYGAKCkl0IGxvb2tzIGxpa2UgRywgUEcsIGFuZCBQRzEzIHJhdGVkIG1vdmllcyBncm9zcyBtdWNoIGhpZ2hlciBvbiBhdmVyYWdlIHRoYW4gb3RoZXIgTVBBQSByYXRlZCBtb3ZpZXMuIFRoaXMgbWFrZXMgc2Vuc2UgYXMgdGhlc2UgdHlwZXMgb2YgbW92aWVzIGFyZSBtb3JlICJmYW1pbHkgZnJpZW5kbHkiIGFuZCBjYW4gcmVsYXRlIHRvIGEgbXVjaCBiaWdnZXIgYXVkaWVuY2UuIFRodXMsIEkgY3JlYXRlZCBhIGJpbmFyeSB2YXJpYWJsZSBgZmFtaWx5X2ZyaWVuZGx5YCBhbmQgaW50ZXJhY3RlZCBpdCB3aXRoIGB3aW5zYCBhbmQgYG5vbWluYXRpb25zYCB0byBzZWUgaWYgdGhlcmUgd2FzIGFueSBtZWFuaW5nZnVsIGludGVyYWN0aW9ucy4gSWYgd2UgbG9vayBhdCB0aGUgZ2dwYWlycywgaXQgbG9va3MgcHJvbWlzaW5nLiAKCmBgYHtyfQojIFBsb3R0aW5nIENvcnJlbGF0aW9uIFBsb3RzIG9mIHRoZSBkaWZmZXJlbnQgUmV2aWV3IG51bWJlciBDb2x1bW5zOgpnZ3BhaXJzKGRmW2MoJ2ZhbWlseV9mcmllbmRseScsICd3aW5zJywgJ2ZhbWlseV9mcmllbmRseV93aW5zJywgJ0dyb3NzJyldKQpnZ3BhaXJzKGRmW2MoJ2ZhbWlseV9mcmllbmRseScsICdub21pbmF0aW9ucycsICdmYW1pbHlfZnJpZW5kbHlfbm9taW5hdGlvbnMnLCAnR3Jvc3MnKV0pCmBgYAoKQW1vbmcgb3RoZXIgdGhpbmdzIGZyb20gdGhlIGBnZ3BhaXJzYCBncmFwaCBhYm92ZSwgeW91IGNhbiBzZWUgdGhhdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBgR3Jvc3NgIHZzLiBgd2luc2AgYW5kIGBub21pbmF0aW9uc2AgaXMgZmFpcmx5IGhpZ2ggKGFwcHJveGltYXRlbHkgMC4zIGZvciBlYWNoKSwgYnV0IGludGVyYWN0ZWQgd2l0aCBgZmFtaWx5X2ZyaWVuZGx5YCwgdGhlIGNvcnJlbGF0aW9uIGdvZXMgdXAgdG8gYWxtb3N0IDAuNSEgVGh1cywgaXQgc2VlbXMgbGlrZSB0aGVyZSBpcyBhIG1lYW5pbmdmdWwgaW50ZXJhY3Rpb24gYmV0d2VlbiBgd2luc2AsIGBub21pbmF0aW9uc2AgYW5kIGBmYW1pbHlfZnJpZW5kbHlgLiBUaGlzIGVzc2VudGlhbGx5IG1lYW5zIHRoYXQgZm9yIGZhbWlseSBmcmllbmRseSBtb3ZpZXMsIHdpbm5pbmcgYW5kL29yIGJlaW5nIG5vbWluYXRlZCBmb3IgYW4gYXdhcmQgaGFzIGZ1bmN0aW9uYWxseSBkaWZmZXJlbnQgcmVsYXRpb25zaGlwIHdpdGggR3Jvc3MgdGhhbiBub24tZmFtaWx5IGZyaWVuZGx5IG1vdmllcy4gCgoqKlEqKjogQ29tbWVudCBvbiB0aGUgZmluYWwgUk1TRSB2YWx1ZXMgeW91IG9idGFpbmVkLCBhbmQgd2hhdCB5b3UgbGVhcm5lZCB0aHJvdWdoIHRoZSBjb3Vyc2Ugb2YgdGhpcyBwcm9qZWN0LgoKKipBKio6CkZvciBNb2RlbCA1LCB3ZSBjYW4gZGlyZWN0bHkgY29tcGFyZSB0aGUgYmVzdCB0ZXN0IFJNU0VzOgoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2dwbG90KGRhdGE9ZGZfZXZhbF9tb2Q1LCBhZXMoeD10cmFpbl9zaXplLCB5PXJtc2VzKSkgKwogIGdlb21fbGluZShhZXMoY29sb3I9cm1zZV90eXBlLCBncm91cD1ybXNlX3R5cGUpKSArCiAgZ2d0aXRsZSgnTW9kZWwgNTogVHJhaW5pbmcgYW5kIFRlc3QgUk1TRScpCgpwcmludChldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UpCnByaW50KGV2YWxfbW9kZWwzJGJlc3RfdGVzdF9ybXNlKQpwcmludChldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UpCmBgYAoKVGhlIGltcHJvdmVtZW50IG9mIE1vZGVsIDUgY29tcGFyZWQgdG8gTW9kZWwgNCB3YXM6CgpgYGB7cn0KaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDUgPSBldmFsX21vZGVsNSRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWw0JGJlc3RfdGVzdF9ybXNlCnBlcmNlbnRhZ2VfaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDUgPSAxLjAgKiBhYnMoZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UgLSBldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSkgLyBldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZQpwcmludCgnSW1wcm92ZW1lbnQgaW4gUk1TRScpCnByaW50KGltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWw1KQoKIyBBcyBhIHBlcmNlbnRhZ2UKcHJpbnQocGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNSkKYGBgCgpOb3RpY2UgdGhhdCB0aGUgaW1wcm92ZW1lbnQgaW4gTW9kZWwgNSB3YXMgYHIge2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWw0fWAgd2hpY2ggcmVwcmVzZW50cyBhIGZ1cnRoZXIgYHIge3BlcmNlbnRhZ2VfaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDUgKiAxMDB9YCBwZXJjZW50IGltcHJvdmVtZW50IG92ZXIgTW9kZWwgNCEgSW4gYWRkaXRpb24sIG5vdGljZSB0aGF0IHRoZSBhbGwtaW4gaW1wcm92ZW1lbnQgb2YgTW9kZWwgNSBjb21wYXJlZCB0byBNb2RlbCAxIGlzIGByIHtldmFsX21vZGVsNSRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlfWAgd2hpY2ggcmVwcmVzZW50cyBhIGByIHsxLjAgKiBhYnMoZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UgLSBldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkgLyBldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSAqIDEwMH1gIHBlcmNlbnRhZ2UgaW1wcm92ZW1lbnQhIAoKYGBge3J9CiMgQmVzdCB0ZXN0IFJNU0UKYmVzdF90ZXN0cm1zZV9tb2Q1ID0gZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UKYmVzdF90cmFpbl9zaXplX21vZDUgPSBldmFsX21vZGVsNSRiZXN0X3RyYWluX3NpemUKYGBgCgpUaGUgYmVzdCBtZWFuIHRlc3QgUk1TRSB2YWx1ZSBJIG9ic2VydmVkIGZvciBNb2RlbCA1IHdhcyBgciB7YmVzdF90ZXN0cm1zZV9tb2Q1fWAgYW5kIEkgb2JzZXJ2ZWQgdGhhdCBhdCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgYHIge2Jlc3RfdHJhaW5fc2l6ZV9tb2Q1ICogMTAwfWAgcGVyY2VudC4gTm90aWNlIGFnYWluLCB3ZSBzZWUgYSBzaW1pbGFyIHBhdHRlcm4gYXMgb3RoZXIgbW9kZWxzLiBGcm9tIHRoZSBncmFwaCBhYm92ZSwgbm90aWNlIHRoYXQgZm9yIE1vZGVsIDUgdGhhdCB3ZSBzZWUgdGhhdCBUZXN0IFJNU0UgZ2VuZXJhbGx5IGRlY3JlYXNlcyB3aXRoIGhpZ2hlciB0cmFpbmluZyBzZXQuIEFnYWluLCB0aGlzIG1ha2VzIHNlbnNlIGJlY2F1c2Ugd2l0aCB2ZXJ5IGxvdyB0cmFpbmluZyBzZXQgc2l6ZSwgdGhlIG1vZGVsIHdpbGwgb3ZlcmZpdCB0aGUgbW9yZSBsaW1pdGVkIGRhdGEuCgpUaGlzIHByb2plY3Qgd2FzIGltbWVuc2VseSB1c2VmdWwgaW4gZ2V0dGluZyBleHBlcmllbmNlIGludG8gZGlnZ2luZyBkZWVwZXIgYWJvdXQgYnVpbGRpbmcgbW9kZWxzLiBFdmVuIHdpdGggdGhpcyByZWxhdGl2ZWx5IGxpbWl0ZWQgZGF0YSBzZXQ7IG9uIHRoZSBvcmRlciBvZiAxMDAwcyBvZiBvYnNlcnZhdGlvbnMgYW5kIDEwMHMgb2YgZmVhdHVyZXMsIHRoZXJlIGlzIHByYWN0aWNhbGx5IGluZmluaXRlIHdheXMgd2UgY2FuIGVuZ2luZWVyIG5ldyBmZWF0dXJlcyBhbmQgdGh1cyBkbyBmZWF0dXJlIHNlbGVjdGlvbiBhcyB0byB3aGF0IHdlIHdhbnQgdG8gcHV0IGludG8gb3VyIG1vZGVscy4gCgpPbmUgdGhpbmcgSSBkZWZpbml0ZWx5IGxlYXJuZWQgd2FzIHRoYXQgZmVhdHVyZSBlbmdpbmVlcmluZyBpcyB1c2VmdWwuIEVzcGVjaWFsbHkgd2l0aCBsaW5lYXIgcmVncmVzc2lvbiwgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFuIG91dGNvbWUgdmFyaWFibGUgYW5kIHBvdGVudGlhbCBmZWF0dXJlcyBpcyB2ZXJ5IHJhcmVseSB0cnVseSBsaW5lYXIuIEV4cGxvcmluZyB3aGljaCB0cmFuc2Zvcm1hdGlvbnMgYW5kIGludGVyYWN0aW9ucyBpbXByb3ZlIHRoZSBtb2RlbCBpcyBpbXBvcnRhbnQuIEhvd2V2ZXIsIHRoaXMgaXMgbm90IHJpc2tsZXNzISEhIEJlY2F1c2Ugb2YgY3Vyc2Ugb2YgZGltZW5zaW9uYWxpdHkgYW5kIGJpYXMvdmFyaWFuY2UgdHJhZGVvZmYsIHlvdSBjYW5ub3QganVzdCBwdXQgd2hhdGV2ZXIgeW91IHdhbnQgaW50byB0aGUgbW9kZWwuIFRodXMsIHRoZSBob2xkLW91dCB0ZXN0IHNldCBpcyBpbXBvcnRhbnQuIAoKQW5vdGhlciB0aGluZyB0aGF0IEkgbGVhcm5lZCBpcyB0aGUgaW1wb3J0YW5jZSBvZiB0cmFpbmluZy90ZXN0IHNwbGl0LCBhbmQgSSBmb3VuZCB0aGUgcmVsYXRpb25zaGlwIHdpdGggc2l6ZSBvZiB0cmFpbmluZyBzZXQgaW50ZXJlc3RpbmcuIFRoZSB0ZXN0IGVycm9yIHNlZW1lZCB0byBnZW5lcmFsbHkgZGVjcmVhc2UgYW5kIGdlbmVyYWxseSB0aGUgbG93ZXN0IHRlc3QgZXJyb3Igd2FzIGZvdW5kIHdoZW4gdHJhaW5lZCBvbiAxMDAlIG9mIHRyYWluaW5nIHNldC4gVGhpcyBpcyBhIGtleSBsZXNzb24gc2luY2UgaWYgeW91IHRyYWluIG9uIHNtYWxsIHNhbXBsZSBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBzbWFsbCB0cmFpbmluZyBzZXQgKGV2ZW4gd2l0aCBhIHJlbGF0aXZlbHkgaW5mbGV4aWJsZSBtZXRob2QgbGlrZSBsaW5lYXIgcmVncmVzc2lvbiEpIFRodXMsIGFsd2F5cyBiZSBjYXJlZnVsIG9mIG92ZXJmaXR0aW5nIGFuZCBuZXZlciB0cnVzdCB0aGUgdHJhaW5pbmcgZXJyb3IuCgpJZiBJIHdhbnRlZCB0byB0YWtlIHRoaXMgYSBzdGVwIGZvcndhcmQsIEkgd291bGQgZGVmaW5pdGVseSB3YW50IHRvIGluY2x1ZGUgc29tZSBraW5kIG9mIHZhbGlkYXRpb24gc2V0L3Byb2NlZHVyZSAoZS5nLiBLLWZvbGQgdmFsaWRhdGlvbikuIEhvd2V2ZXIsIGdpdmVuIHRoZSBzY29wZSBvZiB0aGlzIHByb2plY3QgYXNzaWdubWVudCBhbmQgdGhpcyBQaWF6emEgcG9zdDogaHR0cHM6Ly9waWF6emEuY29tL2NsYXNzL2o2Z3Q3eWN4Nm5rMTQ1P2NpZD0xMTIzLCBJIGRpZCBub3QgZG8gc28gaGVyZS4gSG93ZXZlciwgdGhpcyB3b3VsZCBiZSBvbmUgdG9vbCB0byBoZWxwIHVzIGZpZ3VyZSBvdXQgdGhlIGJlc3QgZmVhdHVyZXMgdG8gaW5jbHVkZSBpbiBvdXIgbW9kZWwuIEZvciBleGFtcGxlLCB0aGlzIHdvdWxkIGJlIGEgZ29vZCB0b29sIHRvIGhlbHAgdXMgZmlndXJlIG91dCB3aGF0IHBvd2VyIHRyYW5zZm9ybWF0aW9ucyB0byB1c2UuIA==